mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 14:24:45 +00:00
Merge branch 'trunk' into no-implicit-zig-allocator
This commit is contained in:
commit
f55af7282c
20 changed files with 944 additions and 752 deletions
|
@ -4,7 +4,7 @@ const expectEqual = std.testing.expectEqual;
|
|||
|
||||
// This whole module is a translation of grapheme breaks from
|
||||
// the https://github.com/JuliaStrings/utf8proc library.
|
||||
// Thanks so much to those developer!
|
||||
// Thanks so much to those developers!
|
||||
//
|
||||
// The only function this file exposes is `isGraphemeBreak`
|
||||
//
|
||||
|
|
|
@ -19,18 +19,21 @@ comptime {
|
|||
exportStrFn(str.countSegments, "count_segments");
|
||||
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
|
||||
exportStrFn(str.startsWith, "starts_with");
|
||||
exportStrFn(str.strConcat, "concat");
|
||||
exportStrFn(str.endsWith, "ends_with");
|
||||
exportStrFn(str.strConcatC, "concat");
|
||||
exportStrFn(str.strNumberOfBytes, "number_of_bytes");
|
||||
exportStrFn(str.strFromIntC, "from_int");
|
||||
}
|
||||
|
||||
// Export helpers - Must be run inside a comptime
|
||||
fn exportBuiltinFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
|
||||
@export(fn_target, .{ .name = "roc_builtins." ++ fn_name, .linkage = .Strong });
|
||||
fn exportBuiltinFn(comptime func: anytype, comptime funcName: []const u8) void {
|
||||
@export(func, .{ .name = "roc_builtins." ++ funcName, .linkage = .Strong });
|
||||
}
|
||||
fn exportNumFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
|
||||
exportBuiltinFn(fn_target, "num." ++ fn_name);
|
||||
fn exportNumFn(comptime func: anytype, comptime funcName: []const u8) void {
|
||||
exportBuiltinFn(func, "num." ++ funcName);
|
||||
}
|
||||
fn exportStrFn(comptime fn_target: anytype, comptime fn_name: []const u8) void {
|
||||
exportBuiltinFn(fn_target, "str." ++ fn_name);
|
||||
fn exportStrFn(comptime func: anytype, comptime funcName: []const u8) void {
|
||||
exportBuiltinFn(func, "str." ++ funcName);
|
||||
}
|
||||
|
||||
// Run all tests in imported modules
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -28,3 +28,6 @@ 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_NUMBER_OF_BYTES: &str = "roc_builtins.str.number_of_bytes";
|
||||
pub const STR_FROM_INT: &str = "roc_builtins.str.from_int";
|
||||
|
|
|
@ -417,12 +417,24 @@ 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,
|
||||
top_level_function(vec![str_type()], Box::new(int_type())),
|
||||
);
|
||||
|
||||
// fromInt : Int -> Str
|
||||
add_type(
|
||||
Symbol::STR_FROM_INT,
|
||||
top_level_function(vec![int_type()], Box::new(str_type())),
|
||||
);
|
||||
|
||||
// List module
|
||||
|
||||
// get : List elem, Int -> Result elem [ OutOfBounds ]*
|
||||
|
|
|
@ -1096,12 +1096,24 @@ 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 };
|
||||
unique_function(vec![str_type(star1)], int_type(star2))
|
||||
});
|
||||
|
||||
// fromInt : Attr * Int -> Attr * Str
|
||||
add_type(Symbol::STR_FROM_INT, {
|
||||
let_tvars! { star1, star2 };
|
||||
unique_function(vec![int_type(star1)], str_type(star2))
|
||||
});
|
||||
|
||||
// Result module
|
||||
|
||||
// map : Attr * (Result (Attr a e))
|
||||
|
|
|
@ -54,7 +54,9 @@ 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::STR_FROM_INT => str_from_int,
|
||||
Symbol::LIST_LEN => list_len,
|
||||
Symbol::LIST_GET => list_get,
|
||||
Symbol::LIST_SET => list_set,
|
||||
|
@ -989,6 +991,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();
|
||||
|
@ -1009,6 +1031,26 @@ fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
)
|
||||
}
|
||||
|
||||
/// Str.fromInt : Int -> Str
|
||||
fn str_from_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let int_var = var_store.fresh();
|
||||
let str_var = var_store.fresh();
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::StrFromInt,
|
||||
args: vec![(int_var, Var(Symbol::ARG_1))],
|
||||
ret_var: str_var,
|
||||
};
|
||||
|
||||
defn(
|
||||
symbol,
|
||||
vec![(int_var, Symbol::ARG_1)],
|
||||
var_store,
|
||||
body,
|
||||
str_var,
|
||||
)
|
||||
}
|
||||
|
||||
/// List.concat : List elem, List elem -> List elem
|
||||
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
|
|
|
@ -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_from_int, str_number_of_bytes, str_split,
|
||||
str_starts_with, CHAR_LAYOUT,
|
||||
};
|
||||
use crate::llvm::compare::{build_eq, build_neq};
|
||||
use crate::llvm::convert::{
|
||||
|
@ -2426,15 +2427,25 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
let inplace = get_inplace_from_layout(layout);
|
||||
|
||||
str_concat(env, inplace, scope, parent, args[0], args[1])
|
||||
str_concat(env, inplace, scope, args[0], args[1])
|
||||
}
|
||||
StrStartsWith => {
|
||||
// Str.startsWith : Str, Str -> Bool
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
let inplace = get_inplace_from_layout(layout);
|
||||
str_starts_with(env, scope, args[0], args[1])
|
||||
}
|
||||
StrEndsWith => {
|
||||
// Str.startsWith : Str, Str -> Bool
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
str_starts_with(env, inplace, scope, parent, args[0], args[1])
|
||||
str_ends_with(env, scope, args[0], args[1])
|
||||
}
|
||||
StrFromInt => {
|
||||
// Str.fromInt : Int -> Str
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
str_from_int(env, scope, args[0])
|
||||
}
|
||||
StrSplit => {
|
||||
// Str.split : Str, Str -> List Str
|
||||
|
@ -2442,14 +2453,13 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
|
||||
let inplace = get_inplace_from_layout(layout);
|
||||
|
||||
str_split(env, scope, parent, inplace, args[0], args[1])
|
||||
str_split(env, scope, inplace, args[0], args[1])
|
||||
}
|
||||
StrIsEmpty => {
|
||||
// Str.isEmpty : Str -> Str
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let wrapper_ptr = ptr_from_symbol(scope, args[0]);
|
||||
let len = str_len(env, parent, *wrapper_ptr);
|
||||
let len = str_number_of_bytes(env, scope, args[0]);
|
||||
let is_zero = env.builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
len,
|
||||
|
@ -2462,7 +2472,7 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
// Str.countGraphemes : Str -> Int
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
str_count_graphemes(env, scope, parent, args[0])
|
||||
str_count_graphemes(env, scope, args[0])
|
||||
}
|
||||
ListLen => {
|
||||
// List.len : List * -> Int
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::llvm::build::{
|
|||
};
|
||||
use crate::llvm::compare::build_eq;
|
||||
use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type};
|
||||
use crate::llvm::refcounting::decrement_refcount_layout;
|
||||
use crate::llvm::refcounting::{decrement_refcount_layout, increment_refcount_layout};
|
||||
use inkwell::builder::Builder;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::types::{BasicTypeEnum, PointerType};
|
||||
|
@ -1318,6 +1318,85 @@ pub fn list_keep_if_help<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
|
||||
/// List.map : List before, (before -> after) -> List after
|
||||
macro_rules! list_map_help {
|
||||
($env:expr, $layout_ids:expr, $inplace:expr, $parent:expr, $func:expr, $func_layout:expr, $list:expr, $list_layout:expr, $function_ptr:expr, $function_return_layout: expr, $closure_info:expr) => {{
|
||||
let layout_ids = $layout_ids;
|
||||
let inplace = $inplace;
|
||||
let parent = $parent;
|
||||
let func = $func;
|
||||
let func_layout = $func_layout;
|
||||
let list = $list;
|
||||
let list_layout = $list_layout;
|
||||
let function_ptr = $function_ptr;
|
||||
let function_return_layout = $function_return_layout;
|
||||
let closure_info : Option<(&Layout, BasicValueEnum)> = $closure_info;
|
||||
|
||||
|
||||
let non_empty_fn = |elem_layout: &Layout<'a>,
|
||||
len: IntValue<'ctx>,
|
||||
list_wrapper: StructValue<'ctx>| {
|
||||
let ctx = $env.context;
|
||||
let builder = $env.builder;
|
||||
|
||||
let ret_list_ptr = allocate_list($env, inplace, function_return_layout, len);
|
||||
|
||||
let elem_type = basic_type_from_layout($env.arena, ctx, elem_layout, $env.ptr_bytes);
|
||||
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
|
||||
|
||||
let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type);
|
||||
|
||||
let list_loop = |index, before_elem| {
|
||||
increment_refcount_layout($env, parent, layout_ids, before_elem, elem_layout);
|
||||
|
||||
let arguments = match closure_info {
|
||||
Some((closure_data_layout, closure_data)) => {
|
||||
increment_refcount_layout( $env, parent, layout_ids, closure_data, closure_data_layout);
|
||||
|
||||
bumpalo::vec![in $env.arena; before_elem, closure_data]
|
||||
}
|
||||
None => bumpalo::vec![in $env.arena; before_elem],
|
||||
};
|
||||
|
||||
|
||||
let call_site_value = builder.build_call(function_ptr, &arguments, "map_func");
|
||||
|
||||
// set the calling convention explicitly for this call
|
||||
call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV);
|
||||
|
||||
let after_elem = call_site_value
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
|
||||
|
||||
// The pointer to the element in the mapped-over list
|
||||
let after_elem_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(ret_list_ptr, &[index], "load_index_after_list")
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(after_elem_ptr, after_elem);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(builder, ctx, parent, list_ptr, len, "#index", list_loop);
|
||||
|
||||
let result = store_list($env, ret_list_ptr, len);
|
||||
|
||||
// decrement the input list and function (if it's a closure)
|
||||
decrement_refcount_layout($env, parent, layout_ids, list, list_layout);
|
||||
decrement_refcount_layout($env, parent, layout_ids, func, func_layout);
|
||||
|
||||
if let Some((closure_data_layout, closure_data)) = closure_info {
|
||||
decrement_refcount_layout( $env, parent, layout_ids, closure_data, closure_data_layout);
|
||||
}
|
||||
|
||||
result
|
||||
};
|
||||
|
||||
if_list_is_not_empty($env, parent, non_empty_fn, list, list_layout, "List.map")
|
||||
}};
|
||||
}
|
||||
|
||||
/// List.map : List before, (before -> after) -> List after
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn list_map<'a, 'ctx, 'env>(
|
||||
|
@ -1332,56 +1411,24 @@ pub fn list_map<'a, 'ctx, 'env>(
|
|||
) -> BasicValueEnum<'ctx> {
|
||||
match (func, func_layout) {
|
||||
(BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, ret_elem_layout)) => {
|
||||
let non_empty_fn = |elem_layout: &Layout<'a>,
|
||||
len: IntValue<'ctx>,
|
||||
list_wrapper: StructValue<'ctx>| {
|
||||
let ctx = env.context;
|
||||
let builder = env.builder;
|
||||
|
||||
let ret_list_ptr = allocate_list(env, inplace, ret_elem_layout, len);
|
||||
|
||||
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
|
||||
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
|
||||
|
||||
let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type);
|
||||
|
||||
let list_loop = |index, before_elem| {
|
||||
let call_site_value =
|
||||
builder.build_call(func_ptr, env.arena.alloc([before_elem]), "map_func");
|
||||
|
||||
// set the calling convention explicitly for this call
|
||||
call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV);
|
||||
|
||||
let after_elem = call_site_value
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
|
||||
|
||||
// The pointer to the element in the mapped-over list
|
||||
let after_elem_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(ret_list_ptr, &[index], "load_index_after_list")
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(after_elem_ptr, after_elem);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(builder, ctx, parent, list_ptr, len, "#index", list_loop);
|
||||
|
||||
let result = store_list(env, ret_list_ptr, len);
|
||||
|
||||
decrement_refcount_layout(env, parent, layout_ids, list, list_layout);
|
||||
|
||||
result
|
||||
};
|
||||
|
||||
if_list_is_not_empty(env, parent, non_empty_fn, list, list_layout, "List.map")
|
||||
list_map_help!(
|
||||
env,
|
||||
layout_ids,
|
||||
inplace,
|
||||
parent,
|
||||
func,
|
||||
func_layout,
|
||||
list,
|
||||
list_layout,
|
||||
func_ptr,
|
||||
ret_elem_layout,
|
||||
None
|
||||
)
|
||||
}
|
||||
(BasicValueEnum::StructValue(ptr_and_data), Layout::Closure(_, _, ret_elem_layout)) => {
|
||||
let non_empty_fn = |elem_layout: &Layout<'a>,
|
||||
len: IntValue<'ctx>,
|
||||
list_wrapper: StructValue<'ctx>| {
|
||||
let ctx = env.context;
|
||||
(
|
||||
BasicValueEnum::StructValue(ptr_and_data),
|
||||
Layout::Closure(_, closure_layout, ret_elem_layout),
|
||||
) => {
|
||||
let builder = env.builder;
|
||||
|
||||
let func_ptr = builder
|
||||
|
@ -1393,43 +1440,21 @@ pub fn list_map<'a, 'ctx, 'env>(
|
|||
.build_extract_value(ptr_and_data, 1, "closure_data")
|
||||
.unwrap();
|
||||
|
||||
let ret_list_ptr = allocate_list(env, inplace, ret_elem_layout, len);
|
||||
let closure_data_layout = closure_layout.as_block_of_memory_layout();
|
||||
|
||||
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
|
||||
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
|
||||
|
||||
let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type);
|
||||
|
||||
let list_loop = |index, before_elem| {
|
||||
let call_site_value = builder.build_call(
|
||||
list_map_help!(
|
||||
env,
|
||||
layout_ids,
|
||||
inplace,
|
||||
parent,
|
||||
func,
|
||||
func_layout,
|
||||
list,
|
||||
list_layout,
|
||||
func_ptr,
|
||||
env.arena.alloc([before_elem, closure_data]),
|
||||
"map_func",
|
||||
);
|
||||
|
||||
// set the calling convention explicitly for this call
|
||||
call_site_value.set_call_convention(crate::llvm::build::FAST_CALL_CONV);
|
||||
|
||||
let after_elem = call_site_value
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
|
||||
|
||||
// The pointer to the element in the mapped-over list
|
||||
let after_elem_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(ret_list_ptr, &[index], "load_index_after_list")
|
||||
};
|
||||
|
||||
// Mutate the new array in-place to change the element.
|
||||
builder.build_store(after_elem_ptr, after_elem);
|
||||
};
|
||||
|
||||
incrementing_elem_loop(builder, ctx, parent, list_ptr, len, "#index", list_loop);
|
||||
|
||||
store_list(env, ret_list_ptr, len)
|
||||
};
|
||||
|
||||
if_list_is_not_empty(env, parent, non_empty_fn, list, list_layout, "List.map")
|
||||
ret_elem_layout,
|
||||
Some((&closure_data_layout, closure_data))
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
unreachable!(
|
||||
|
|
|
@ -1,62 +1,41 @@
|
|||
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, load_list_ptr, 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, IntValue, StructValue};
|
||||
use inkwell::AddressSpace;
|
||||
use roc_builtins::bitcode;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
|
||||
use super::build::load_symbol;
|
||||
|
||||
pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8);
|
||||
|
||||
/// Str.split : Str, Str -> List Str
|
||||
pub fn str_split<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
inplace: InPlace,
|
||||
str_symbol: Symbol,
|
||||
delimiter_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
|
||||
let str_ptr = ptr_from_symbol(scope, str_symbol);
|
||||
let delimiter_ptr = ptr_from_symbol(scope, delimiter_symbol);
|
||||
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
|
||||
let delim_i128 = str_symbol_to_i128(env, scope, delimiter_symbol);
|
||||
|
||||
let str_wrapper_type = BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes));
|
||||
|
||||
load_str(
|
||||
env,
|
||||
parent,
|
||||
*str_ptr,
|
||||
str_wrapper_type,
|
||||
|str_bytes_ptr, str_len, _str_smallness| {
|
||||
load_str(
|
||||
env,
|
||||
parent,
|
||||
*delimiter_ptr,
|
||||
str_wrapper_type,
|
||||
|delimiter_bytes_ptr, delimiter_len, _delimiter_smallness| {
|
||||
let segment_count = call_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
BasicValueEnum::PointerValue(str_bytes_ptr),
|
||||
BasicValueEnum::IntValue(str_len),
|
||||
BasicValueEnum::PointerValue(delimiter_bytes_ptr),
|
||||
BasicValueEnum::IntValue(delimiter_len),
|
||||
],
|
||||
&[str_i128.into(), delim_i128.into()],
|
||||
&bitcode::STR_COUNT_SEGMENTS,
|
||||
)
|
||||
.into_int_value();
|
||||
|
||||
// a pointer to the elements
|
||||
let ret_list_ptr =
|
||||
allocate_list(env, inplace, &Layout::Builtin(Builtin::Str), segment_count);
|
||||
let ret_list_ptr = allocate_list(env, inplace, &Layout::Builtin(Builtin::Str), segment_count);
|
||||
|
||||
// get the RocStr type defined by zig
|
||||
let roc_str_type = env.module.get_struct_type("str.RocStr").unwrap();
|
||||
|
@ -73,44 +52,15 @@ pub fn str_split<'a, 'ctx, 'env>(
|
|||
&[
|
||||
ret_list_ptr_zig_rocstr,
|
||||
BasicValueEnum::IntValue(segment_count),
|
||||
BasicValueEnum::PointerValue(str_bytes_ptr),
|
||||
BasicValueEnum::IntValue(str_len),
|
||||
BasicValueEnum::PointerValue(delimiter_bytes_ptr),
|
||||
BasicValueEnum::IntValue(delimiter_len),
|
||||
str_i128.into(),
|
||||
delim_i128.into(),
|
||||
],
|
||||
&bitcode::STR_STR_SPLIT_IN_PLACE,
|
||||
);
|
||||
|
||||
store_list(env, ret_list_ptr, segment_count)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
fn cast_to_zig_str(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
str_as_struct: StructValue<'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
// get the RocStr type defined by zig
|
||||
let roc_str_type = env.module.get_struct_type("str.RocStr").unwrap();
|
||||
|
||||
// convert `{ *mut u8, i64 }` to `RocStr`
|
||||
builder.build_bitcast(str_as_struct, roc_str_type, "convert_to_zig_rocstr");
|
||||
}
|
||||
|
||||
fn cast_from_zig_str(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
str_as_struct: StructValue<'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let ret_type = BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes));
|
||||
|
||||
// convert `RocStr` to `{ *mut u8, i64 }`
|
||||
builder.build_bitcast(str_as_struct, ret_type, "convert_from_zig_rocstr");
|
||||
}
|
||||
*/
|
||||
|
||||
fn str_symbol_to_i128<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
|
@ -172,7 +122,6 @@ pub fn str_concat<'a, 'ctx, 'env>(
|
|||
env: &Env<'a, 'ctx, 'env>,
|
||||
inplace: InPlace,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
_parent: FunctionValue<'ctx>,
|
||||
str1_symbol: Symbol,
|
||||
str2_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
|
@ -201,223 +150,53 @@ pub fn str_concat<'a, 'ctx, 'env>(
|
|||
zig_str_to_struct(env, zig_result).into()
|
||||
}
|
||||
|
||||
/// Obtain the string's length, cast from i8 to usize
|
||||
fn str_len_from_final_byte<'a, 'ctx, 'env>(
|
||||
pub fn str_number_of_bytes<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
final_byte: IntValue<'ctx>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
str_symbol: Symbol,
|
||||
) -> IntValue<'ctx> {
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
let bitmask = ctx.i8_type().const_int(0b0111_1111, false);
|
||||
let len_i8 = builder.build_and(final_byte, bitmask, "small_str_length");
|
||||
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
|
||||
|
||||
builder.build_int_cast(len_i8, env.ptr_int(), "len_as_usize")
|
||||
}
|
||||
|
||||
/// Used by LowLevel::StrIsEmpty
|
||||
pub fn str_len<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
wrapper_ptr: PointerValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
let builder = env.builder;
|
||||
|
||||
let if_small = |final_byte| {
|
||||
let len = str_len_from_final_byte(env, final_byte);
|
||||
|
||||
BasicValueEnum::IntValue(len)
|
||||
};
|
||||
|
||||
let if_big = |_| {
|
||||
let len = big_str_len(
|
||||
builder,
|
||||
builder
|
||||
.build_load(wrapper_ptr, "big_str")
|
||||
.into_struct_value(),
|
||||
);
|
||||
|
||||
BasicValueEnum::IntValue(len)
|
||||
};
|
||||
|
||||
if_small_str(
|
||||
env,
|
||||
parent,
|
||||
wrapper_ptr,
|
||||
if_small,
|
||||
if_big,
|
||||
BasicTypeEnum::IntType(env.ptr_int()),
|
||||
)
|
||||
.into_int_value()
|
||||
}
|
||||
|
||||
fn load_str<'a, 'ctx, 'env, Callback>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
wrapper_ptr: PointerValue<'ctx>,
|
||||
ret_type: BasicTypeEnum<'ctx>,
|
||||
cb: Callback,
|
||||
) -> BasicValueEnum<'ctx>
|
||||
where
|
||||
Callback: Fn(PointerValue<'ctx>, IntValue<'ctx>, Smallness) -> BasicValueEnum<'ctx>,
|
||||
{
|
||||
let builder = env.builder;
|
||||
|
||||
let if_small = |final_byte| {
|
||||
cb(
|
||||
cast_str_wrapper_to_array(env, wrapper_ptr),
|
||||
str_len_from_final_byte(env, final_byte),
|
||||
Smallness::Small,
|
||||
)
|
||||
};
|
||||
|
||||
let if_big = |wrapper_struct| {
|
||||
let list_ptr = load_list_ptr(
|
||||
builder,
|
||||
wrapper_struct,
|
||||
env.context.i8_type().ptr_type(AddressSpace::Generic),
|
||||
);
|
||||
|
||||
cb(
|
||||
list_ptr,
|
||||
big_str_len(builder, wrapper_struct),
|
||||
Smallness::Big,
|
||||
)
|
||||
};
|
||||
|
||||
if_small_str(env, parent, wrapper_ptr, if_small, if_big, ret_type)
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum Smallness {
|
||||
Small,
|
||||
Big,
|
||||
}
|
||||
|
||||
fn cast_str_wrapper_to_array<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
wrapper_ptr: PointerValue<'ctx>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let array_ptr_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
|
||||
// the builtin will always return an u64
|
||||
let length =
|
||||
call_bitcode_fn(env, &[str_i128.into()], &bitcode::STR_NUMBER_OF_BYTES).into_int_value();
|
||||
|
||||
// cast to the appropriate usize of the current build
|
||||
env.builder
|
||||
.build_bitcast(wrapper_ptr, array_ptr_type, "str_as_array_ptr")
|
||||
.into_pointer_value()
|
||||
}
|
||||
|
||||
fn if_small_str<'a, 'ctx, 'env, IfSmallFn, IfBigFn>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
wrapper_ptr: PointerValue<'ctx>,
|
||||
mut if_small: IfSmallFn,
|
||||
mut if_big: IfBigFn,
|
||||
ret_type: BasicTypeEnum<'ctx>,
|
||||
) -> BasicValueEnum<'ctx>
|
||||
where
|
||||
IfSmallFn: FnMut(IntValue<'ctx>) -> BasicValueEnum<'ctx>,
|
||||
IfBigFn: FnMut(StructValue<'ctx>) -> BasicValueEnum<'ctx>,
|
||||
{
|
||||
let builder = env.builder;
|
||||
let ctx = env.context;
|
||||
let byte_array_ptr = cast_str_wrapper_to_array(env, wrapper_ptr);
|
||||
let final_byte_ptr = unsafe {
|
||||
builder.build_in_bounds_gep(
|
||||
byte_array_ptr,
|
||||
&[ctx
|
||||
.i8_type()
|
||||
.const_int(env.small_str_bytes() as u64 - 1, false)],
|
||||
"final_byte_ptr",
|
||||
)
|
||||
};
|
||||
|
||||
let final_byte = builder
|
||||
.build_load(final_byte_ptr, "load_final_byte")
|
||||
.into_int_value();
|
||||
|
||||
let bitmask = ctx.i8_type().const_int(0b1000_0000, false);
|
||||
|
||||
let is_small_i8 = builder.build_int_compare(
|
||||
IntPredicate::NE,
|
||||
ctx.i8_type().const_zero(),
|
||||
builder.build_and(final_byte, bitmask, "is_small"),
|
||||
"is_small_comparison",
|
||||
);
|
||||
|
||||
let is_small = builder.build_int_cast(is_small_i8, ctx.bool_type(), "is_small_as_bool");
|
||||
|
||||
build_basic_phi2(
|
||||
env,
|
||||
parent,
|
||||
is_small,
|
||||
|| if_small(final_byte),
|
||||
|| {
|
||||
if_big(
|
||||
builder
|
||||
.build_load(wrapper_ptr, "load_wrapper_struct")
|
||||
.into_struct_value(),
|
||||
)
|
||||
},
|
||||
ret_type,
|
||||
)
|
||||
}
|
||||
|
||||
fn big_str_len<'ctx>(builder: &Builder<'ctx>, wrapper_struct: StructValue<'ctx>) -> IntValue<'ctx> {
|
||||
builder
|
||||
.build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "big_str_len")
|
||||
.unwrap()
|
||||
.into_int_value()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn str_is_not_empty<'ctx>(env: &Env<'_, 'ctx, '_>, len: IntValue<'ctx>) -> IntValue<'ctx> {
|
||||
env.builder.build_int_compare(
|
||||
IntPredicate::UGT,
|
||||
len,
|
||||
env.ptr_int().const_zero(),
|
||||
"str_len_is_nonzero",
|
||||
)
|
||||
.build_int_cast(length, env.ptr_int(), "len_as_usize")
|
||||
}
|
||||
|
||||
/// Str.startsWith : Str, Str -> Bool
|
||||
pub fn str_starts_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 ctx = env.context;
|
||||
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
|
||||
let prefix_i128 = str_symbol_to_i128(env, scope, prefix_symbol);
|
||||
|
||||
let str_ptr = ptr_from_symbol(scope, str_symbol);
|
||||
let prefix_ptr = ptr_from_symbol(scope, prefix_symbol);
|
||||
|
||||
let ret_type = BasicTypeEnum::IntType(ctx.bool_type());
|
||||
|
||||
load_str(
|
||||
env,
|
||||
parent,
|
||||
*str_ptr,
|
||||
ret_type,
|
||||
|str_bytes_ptr, str_len, _str_smallness| {
|
||||
load_str(
|
||||
env,
|
||||
parent,
|
||||
*prefix_ptr,
|
||||
ret_type,
|
||||
|prefix_bytes_ptr, prefix_len, _prefix_smallness| {
|
||||
call_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
BasicValueEnum::PointerValue(str_bytes_ptr),
|
||||
BasicValueEnum::IntValue(str_len),
|
||||
BasicValueEnum::PointerValue(prefix_bytes_ptr),
|
||||
BasicValueEnum::IntValue(prefix_len),
|
||||
],
|
||||
&[str_i128.into(), prefix_i128.into()],
|
||||
&bitcode::STR_STARTS_WITH,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
/// Str.endsWith : Str, Str -> Bool
|
||||
pub fn str_ends_with<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, '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,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -425,28 +204,26 @@ pub fn str_starts_with<'a, 'ctx, 'env>(
|
|||
pub fn str_count_graphemes<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
parent: FunctionValue<'ctx>,
|
||||
str_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let ctx = env.context;
|
||||
let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
|
||||
|
||||
let sym_str_ptr = ptr_from_symbol(scope, str_symbol);
|
||||
let ret_type = BasicTypeEnum::IntType(ctx.i64_type());
|
||||
|
||||
load_str(
|
||||
env,
|
||||
parent,
|
||||
*sym_str_ptr,
|
||||
ret_type,
|
||||
|str_ptr, str_len, _str_smallness| {
|
||||
call_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
BasicValueEnum::PointerValue(str_ptr),
|
||||
BasicValueEnum::IntValue(str_len),
|
||||
],
|
||||
&[str_i128.into()],
|
||||
&bitcode::STR_COUNT_GRAPEHEME_CLUSTERS,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Str.fromInt : Int -> Str
|
||||
pub fn str_from_int<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
scope: &Scope<'a, 'ctx>,
|
||||
int_symbol: Symbol,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let int = load_symbol(env, scope, &int_symbol);
|
||||
|
||||
let zig_result = call_bitcode_fn(env, &[int], &bitcode::STR_FROM_INT).into_struct_value();
|
||||
|
||||
zig_str_to_struct(env, zig_result).into()
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::llvm::build::{
|
|||
cast_basic_basic, cast_struct_struct, create_entry_block_alloca, set_name, Env, Scope,
|
||||
FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I64,
|
||||
};
|
||||
use crate::llvm::build_list::list_len;
|
||||
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
|
||||
use crate::llvm::convert::{basic_type_from_layout, block_of_memory, ptr_int};
|
||||
use bumpalo::collections::Vec;
|
||||
use inkwell::context::Context;
|
||||
|
@ -367,7 +367,6 @@ fn decrement_refcount_builtin<'a, 'ctx, 'env>(
|
|||
List(memory_mode, element_layout) => {
|
||||
let wrapper_struct = value.into_struct_value();
|
||||
if element_layout.contains_refcounted() {
|
||||
use crate::llvm::build_list::{incrementing_elem_loop, load_list};
|
||||
use inkwell::types::BasicType;
|
||||
|
||||
let ptr_type =
|
||||
|
@ -451,7 +450,6 @@ fn increment_refcount_builtin<'a, 'ctx, 'env>(
|
|||
List(memory_mode, element_layout) => {
|
||||
let wrapper_struct = value.into_struct_value();
|
||||
if element_layout.contains_refcounted() {
|
||||
use crate::llvm::build_list::{incrementing_elem_loop, load_list};
|
||||
use inkwell::types::BasicType;
|
||||
|
||||
let ptr_type =
|
||||
|
|
|
@ -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);
|
||||
|
@ -483,4 +490,29 @@ mod gen_str {
|
|||
fn str_starts_with_false_small_str() {
|
||||
assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_from_int() {
|
||||
assert_evals_to!(
|
||||
r#"Str.fromInt 1234"#,
|
||||
roc_std::RocStr::from_slice("1234".as_bytes()),
|
||||
roc_std::RocStr
|
||||
);
|
||||
assert_evals_to!(
|
||||
r#"Str.fromInt 0"#,
|
||||
roc_std::RocStr::from_slice("0".as_bytes()),
|
||||
roc_std::RocStr
|
||||
);
|
||||
assert_evals_to!(
|
||||
r#"Str.fromInt -1"#,
|
||||
roc_std::RocStr::from_slice("-1".as_bytes()),
|
||||
roc_std::RocStr
|
||||
);
|
||||
|
||||
let max = format!("{}", i64::MAX);
|
||||
assert_evals_to!(r#"Str.fromInt Num.maxInt"#, &max, &'static str);
|
||||
|
||||
let min = format!("{}", i64::MIN);
|
||||
assert_evals_to!(r#"Str.fromInt Num.minInt"#, &min, &'static str);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ pub enum LowLevel {
|
|||
StrConcat,
|
||||
StrIsEmpty,
|
||||
StrStartsWith,
|
||||
StrEndsWith,
|
||||
StrSplit,
|
||||
StrCountGraphemes,
|
||||
StrFromInt,
|
||||
ListLen,
|
||||
ListGetUnsafe,
|
||||
ListSet,
|
||||
|
|
|
@ -673,6 +673,8 @@ define_builtins! {
|
|||
5 STR_SPLIT: "split"
|
||||
6 STR_COUNT_GRAPHEMES: "countGraphemes"
|
||||
7 STR_STARTS_WITH: "startsWith"
|
||||
8 STR_ENDS_WITH: "endsWith"
|
||||
9 STR_FROM_INT: "fromInt"
|
||||
}
|
||||
4 LIST: "List" => {
|
||||
0 LIST_LIST: "List" imported // the List.List type alias
|
||||
|
|
|
@ -547,6 +547,7 @@ 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]),
|
||||
StrFromInt => arena.alloc_slice_copy(&[irrelevant]),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -460,7 +460,7 @@ impl<'a> Layout<'a> {
|
|||
|
||||
pub fn is_refcounted(&self) -> bool {
|
||||
match self {
|
||||
Layout::Builtin(Builtin::List(_, _)) => true,
|
||||
Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) => true,
|
||||
Layout::Builtin(Builtin::Str) => true,
|
||||
Layout::RecursiveUnion(_) => true,
|
||||
Layout::RecursivePointer => true,
|
||||
|
@ -477,12 +477,12 @@ impl<'a> Layout<'a> {
|
|||
match self {
|
||||
Builtin(builtin) => builtin.is_refcounted(),
|
||||
PhantomEmptyStruct => false,
|
||||
Struct(fields) => fields.iter().any(|f| f.is_refcounted()),
|
||||
Struct(fields) => fields.iter().any(|f| f.contains_refcounted()),
|
||||
Union(fields) => fields
|
||||
.iter()
|
||||
.map(|ls| ls.iter())
|
||||
.flatten()
|
||||
.any(|f| f.is_refcounted()),
|
||||
.any(|f| f.contains_refcounted()),
|
||||
RecursiveUnion(_) => true,
|
||||
Closure(_, closure_layout, _) => closure_layout.contains_refcounted(),
|
||||
FunctionPointer(_, _) | RecursivePointer | Pointer(_) => false,
|
||||
|
|
|
@ -917,9 +917,13 @@ fn parse_def_signature<'a>(
|
|||
|
||||
fn loc_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Expr<'a>>> {
|
||||
skip_first!(
|
||||
// If this is a reserved keyword ("if", "then", "case, "when"), then
|
||||
// it is not a function argument!
|
||||
not(reserved_keyword()),
|
||||
// If this is a reserved keyword ("if", "then", "case, "when"),
|
||||
// followed by a blank space, then it is not a function argument!
|
||||
//
|
||||
// (The space is necessary because otherwise we'll get a false
|
||||
// positive on function arguments beginning with keywords,
|
||||
// e.g. `ifBlah` or `isSomething` will register as `if`/`is` keywords)
|
||||
not(and!(reserved_keyword(), space1(min_indent))),
|
||||
// Don't parse operators, because they have a higher precedence than function application.
|
||||
// If we encounter one, we're done parsing function args!
|
||||
move |arena, state| loc_parse_function_arg(min_indent, arena, state)
|
||||
|
|
|
@ -777,24 +777,25 @@ macro_rules! skip_first {
|
|||
use $crate::parser::Fail;
|
||||
|
||||
let original_attempting = state.attempting;
|
||||
let original_state = state.clone();
|
||||
|
||||
match $p1.parse(arena, state) {
|
||||
Ok((_, state)) => match $p2.parse(arena, state) {
|
||||
Ok((out2, state)) => Ok((out2, state)),
|
||||
Err((fail, state)) => Err((
|
||||
Err((fail, _)) => Err((
|
||||
Fail {
|
||||
attempting: original_attempting,
|
||||
..fail
|
||||
},
|
||||
state,
|
||||
original_state,
|
||||
)),
|
||||
},
|
||||
Err((fail, state)) => Err((
|
||||
Err((fail, _)) => Err((
|
||||
Fail {
|
||||
attempting: original_attempting,
|
||||
..fail
|
||||
},
|
||||
state,
|
||||
original_state,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
@ -810,24 +811,25 @@ macro_rules! skip_second {
|
|||
use $crate::parser::Fail;
|
||||
|
||||
let original_attempting = state.attempting;
|
||||
let original_state = state.clone();
|
||||
|
||||
match $p1.parse(arena, state) {
|
||||
Ok((out1, state)) => match $p2.parse(arena, state) {
|
||||
Ok((_, state)) => Ok((out1, state)),
|
||||
Err((fail, state)) => Err((
|
||||
Err((fail, _)) => Err((
|
||||
Fail {
|
||||
attempting: original_attempting,
|
||||
..fail
|
||||
},
|
||||
state,
|
||||
original_state,
|
||||
)),
|
||||
},
|
||||
Err((fail, state)) => Err((
|
||||
Err((fail, _)) => Err((
|
||||
Fail {
|
||||
attempting: original_attempting,
|
||||
..fail
|
||||
},
|
||||
state,
|
||||
original_state,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -814,6 +814,71 @@ mod test_parse {
|
|||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_when() {
|
||||
// Regression test for identifiers beginning with keywords (if/then/else/when/is)
|
||||
let arena = Bump::new();
|
||||
let expected = Var {
|
||||
module_name: "",
|
||||
ident: "whenever",
|
||||
};
|
||||
let actual = parse_expr_with(&arena, "whenever");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_is() {
|
||||
// Regression test for identifiers beginning with keywords (if/then/else/when/is)
|
||||
let arena = Bump::new();
|
||||
let expected = Var {
|
||||
module_name: "",
|
||||
ident: "isnt",
|
||||
};
|
||||
let actual = parse_expr_with(&arena, "isnt");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_if() {
|
||||
// Regression test for identifiers beginning with keywords (if/then/else/when/is)
|
||||
let arena = Bump::new();
|
||||
let expected = Var {
|
||||
module_name: "",
|
||||
ident: "iffy",
|
||||
};
|
||||
let actual = parse_expr_with(&arena, "iffy");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_then() {
|
||||
// Regression test for identifiers beginning with keywords (if/then/else/when/is)
|
||||
let arena = Bump::new();
|
||||
let expected = Var {
|
||||
module_name: "",
|
||||
ident: "thenever",
|
||||
};
|
||||
let actual = parse_expr_with(&arena, "thenever");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_else() {
|
||||
// Regression test for identifiers beginning with keywords (if/then/else/when/is)
|
||||
let arena = Bump::new();
|
||||
let expected = Var {
|
||||
module_name: "",
|
||||
ident: "elsewhere",
|
||||
};
|
||||
let actual = parse_expr_with(&arena, "elsewhere");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parenthetical_var() {
|
||||
let arena = Bump::new();
|
||||
|
@ -1511,6 +1576,32 @@ mod test_parse {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn if_def() {
|
||||
let arena = Bump::new();
|
||||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
let def = Def::Body(
|
||||
arena.alloc(Located::new(0, 0, 0, 4, Identifier("iffy"))),
|
||||
arena.alloc(Located::new(0, 0, 5, 6, Num("5"))),
|
||||
);
|
||||
let loc_def = &*arena.alloc(Located::new(0, 0, 0, 6, def));
|
||||
let defs = &[loc_def];
|
||||
let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice());
|
||||
let loc_ret = Located::new(2, 2, 0, 2, ret);
|
||||
let expected = Defs(defs, arena.alloc(loc_ret));
|
||||
|
||||
assert_parses_to(
|
||||
indoc!(
|
||||
r#"
|
||||
iffy=5
|
||||
|
||||
42
|
||||
"#
|
||||
),
|
||||
expected,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_spaced_def() {
|
||||
let arena = Bump::new();
|
||||
|
@ -2573,6 +2664,41 @@ mod test_parse {
|
|||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn repro_keyword_bug() {
|
||||
// Reproducing this bug requires a bizarre set of things to all be true:
|
||||
//
|
||||
// * Must be parsing a *module* def (nested expr defs don't repro this)
|
||||
// * That top-level module def conatins a def inside it
|
||||
// * That inner def is defining a function
|
||||
// * The name of the inner def begins with a keyword (`if`, `then`, `else`, `when`, `is`)
|
||||
//
|
||||
// If all of these are true, then lookups on that def get skipped over by the parser.
|
||||
// If any one of the above is false, then everything works.
|
||||
|
||||
let arena = Bump::new();
|
||||
let src = indoc!(
|
||||
r#"
|
||||
foo = \list ->
|
||||
isTest = \_ -> 5
|
||||
List.map list isTest
|
||||
"#
|
||||
);
|
||||
let actual = module_defs()
|
||||
.parse(&arena, State::new(src.as_bytes(), Attempting::Module))
|
||||
.map(|tuple| tuple.0);
|
||||
|
||||
// It should occur twice in the debug output - once for the pattern,
|
||||
// and then again for the lookup.
|
||||
let occurrences = format!("{:?}", actual)
|
||||
.split("isTest")
|
||||
.collect::<std::vec::Vec<_>>()
|
||||
.len()
|
||||
- 1;
|
||||
|
||||
assert_eq!(occurrences, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn standalone_module_defs() {
|
||||
use roc_parse::ast::Def::*;
|
||||
|
|
|
@ -4,7 +4,7 @@ app "effect-example" imports [ Effect ] provides [ main ] to "./platform"
|
|||
main : Effect.Effect {} as Fx
|
||||
main =
|
||||
when if 1 == 1 then True 3 else False 3.14 is
|
||||
True 3 -> Effect.putLine "Yay"
|
||||
True n -> Effect.putLine (Str.fromInt n)
|
||||
_ -> Effect.putLine "Yay"
|
||||
|
||||
# main : Effect.Effect {} as Fx
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue