add Str.endsWith

This commit is contained in:
Folkert 2020-12-03 19:16:50 +01:00
parent e8583a72fb
commit 22d798b7c7
12 changed files with 121 additions and 15 deletions

View file

@ -19,6 +19,7 @@ comptime {
exportStrFn(str.countSegments, "count_segments");
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
exportStrFn(str.startsWith, "starts_with");
exportStrFn(str.endsWith, "ends_with");
exportStrFn(str.strConcat, "concat");
exportStrFn(str.strLen, "len");
}

View file

@ -582,18 +582,53 @@ test "startsWith: 12345678912345678910 starts with 123456789123456789" {
expect(startsWith(str, prefix));
}
// Str.split
// Str.endsWith
pub fn strSplit(ptr_size: u32, result_in_place: InPlace, string: RocStr, pattern: RocStr) callconv(.C) [*]RocStr {
return switch (ptr_size) {
// 4 => strSplitHelp(i32, result_in_place, arg1, arg2),
8 => strSplitHelp(i64, result_in_place, arg1, arg2),
else => unreachable,
};
pub fn endsWith(string: RocStr, suffix: RocStr) callconv(.C) bool {
const bytes_len = string.len();
const bytes_ptr = string.as_u8_ptr();
const suffix_len = suffix.len();
const suffix_ptr = suffix.as_u8_ptr();
if (suffix_len > bytes_len) {
return false;
}
const offset: usize = bytes_len - suffix_len;
var i: usize = 0;
while (i < suffix_len) {
if (bytes_ptr[i + offset] != suffix_ptr[i]) {
return false;
}
i += 1;
}
return true;
}
fn strSplitHelp(comptime T: type, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) [*]RocStr {
return [_]u8{};
test "endsWith: foo ends with oo" {
const foo = RocStr.init("foo", 3);
const oo = RocStr.init("oo", 2);
expect(endsWith(foo, oo));
}
test "endsWith: 123456789123456789 ends with 123456789123456789" {
const str = RocStr.init("123456789123456789", 18);
expect(endsWith(str, str));
}
test "endsWith: 12345678912345678910 ends with 345678912345678910" {
const str = RocStr.init("12345678912345678910", 20);
const suffix = RocStr.init("345678912345678910", 18);
expect(endsWith(str, suffix));
}
test "endsWith: hello world ends with world" {
const str = RocStr.init("hello world", 11);
const suffix = RocStr.init("world", 5);
expect(endsWith(str, suffix));
}
// Str.concat

View file

@ -28,4 +28,5 @@ pub const STR_CONCAT: &str = "roc_builtins.str.concat";
pub const STR_STR_SPLIT_IN_PLACE: &str = "roc_builtins.str.str_split_in_place";
pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";
pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with";
pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with";
pub const STR_LEN: &str = "roc_builtins.str.len";

View file

@ -417,6 +417,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
top_level_function(vec![str_type(), str_type()], Box::new(bool_type())),
);
// endsWith : Str, Str -> Bool
add_type(
Symbol::STR_ENDS_WITH,
top_level_function(vec![str_type(), str_type()], Box::new(bool_type())),
);
// countGraphemes : Str -> Int
add_type(
Symbol::STR_COUNT_GRAPHEMES,

View file

@ -1096,6 +1096,12 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![str_type(star1), str_type(star2)], bool_type(star3))
});
// Str.endsWith : Attr * Str, Attr * Str -> Attr * Bool
add_type(Symbol::STR_ENDS_WITH, {
let_tvars! { star1, star2, star3 };
unique_function(vec![str_type(star1), str_type(star2)], bool_type(star3))
});
// Str.countGraphemes : Attr * Str, -> Attr * Int
add_type(Symbol::STR_COUNT_GRAPHEMES, {
let_tvars! { star1, star2 };

View file

@ -54,6 +54,7 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::STR_SPLIT => str_split,
Symbol::STR_IS_EMPTY => str_is_empty,
Symbol::STR_STARTS_WITH => str_starts_with,
Symbol::STR_ENDS_WITH => str_ends_with,
Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes,
Symbol::LIST_LEN => list_len,
Symbol::LIST_GET => list_get,
@ -989,6 +990,26 @@ fn str_starts_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Str.endsWith : Str, Str -> Bool
fn str_ends_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh();
let bool_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::StrEndsWith,
args: vec![(str_var, Var(Symbol::ARG_1)), (str_var, Var(Symbol::ARG_2))],
ret_var: bool_var,
};
defn(
symbol,
vec![(str_var, Symbol::ARG_1), (str_var, Symbol::ARG_2)],
var_store,
body,
bool_var,
)
}
/// Str.countGraphemes : Str -> Int
fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh();

View file

@ -4,7 +4,8 @@ use crate::llvm::build_list::{
list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards,
};
use crate::llvm::build_str::{
str_concat, str_count_graphemes, str_len, str_split, str_starts_with, CHAR_LAYOUT,
str_concat, str_count_graphemes, str_ends_with, str_len, str_split, str_starts_with,
CHAR_LAYOUT,
};
use crate::llvm::compare::{build_eq, build_neq};
use crate::llvm::convert::{
@ -2436,6 +2437,14 @@ fn run_low_level<'a, 'ctx, 'env>(
str_starts_with(env, inplace, scope, parent, args[0], args[1])
}
StrEndsWith => {
// Str.startsWith : Str, Str -> Bool
debug_assert_eq!(args.len(), 2);
let inplace = get_inplace_from_layout(layout);
str_ends_with(env, inplace, scope, parent, args[0], args[1])
}
StrSplit => {
// Str.split : Str, Str -> List Str
debug_assert_eq!(args.len(), 2);

View file

@ -1,12 +1,11 @@
use crate::llvm::build::{
call_bitcode_fn, call_void_bitcode_fn, ptr_from_symbol, Env, InPlace, Scope,
};
use crate::llvm::build_list::{allocate_list, build_basic_phi2, store_list};
use crate::llvm::build_list::{allocate_list, store_list};
use crate::llvm::convert::collection;
use inkwell::builder::Builder;
use inkwell::types::BasicTypeEnum;
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, StructValue};
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout};
@ -208,6 +207,25 @@ pub fn str_starts_with<'a, 'ctx, 'env>(
)
}
/// Str.endsWith : Str, Str -> Bool
pub fn str_ends_with<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_inplace: InPlace,
scope: &Scope<'a, 'ctx>,
_parent: FunctionValue<'ctx>,
str_symbol: Symbol,
prefix_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
let prefix_i128 = str_symbol_to_i128(env, scope, prefix_symbol);
call_bitcode_fn(
env,
&[str_i128.into(), prefix_i128.into()],
&bitcode::STR_ENDS_WITH,
)
}
/// Str.countGraphemes : Str -> Int
pub fn str_count_graphemes<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View file

@ -433,6 +433,13 @@ mod gen_str {
assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool);
}
#[test]
fn str_ends_with() {
assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool);
assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool);
assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool);
}
#[test]
fn str_count_graphemes_small_str() {
assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize);

View file

@ -6,6 +6,7 @@ pub enum LowLevel {
StrConcat,
StrIsEmpty,
StrStartsWith,
StrEndsWith,
StrSplit,
StrCountGraphemes,
ListLen,

View file

@ -673,6 +673,7 @@ define_builtins! {
5 STR_SPLIT: "split"
6 STR_COUNT_GRAPHEMES: "countGraphemes"
7 STR_STARTS_WITH: "startsWith"
8 STR_ENDS_WITH: "endsWith"
}
4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias

View file

@ -547,6 +547,6 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
| NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin => {
arena.alloc_slice_copy(&[irrelevant])
}
StrStartsWith => arena.alloc_slice_copy(&[owned, borrowed]),
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
}
}