Merge branch 'trunk' into no-implicit-zig-allocator

This commit is contained in:
Richard Feldman 2020-12-03 23:35:40 -05:00
commit f55af7282c
20 changed files with 944 additions and 752 deletions

View file

@ -4,7 +4,7 @@ const expectEqual = std.testing.expectEqual;
// This whole module is a translation of grapheme breaks from // This whole module is a translation of grapheme breaks from
// the https://github.com/JuliaStrings/utf8proc library. // 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` // The only function this file exposes is `isGraphemeBreak`
// //

View file

@ -19,18 +19,21 @@ comptime {
exportStrFn(str.countSegments, "count_segments"); exportStrFn(str.countSegments, "count_segments");
exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters"); exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters");
exportStrFn(str.startsWith, "starts_with"); 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 // Export helpers - Must be run inside a comptime
fn exportBuiltinFn(comptime fn_target: anytype, comptime fn_name: []const u8) void { fn exportBuiltinFn(comptime func: anytype, comptime funcName: []const u8) void {
@export(fn_target, .{ .name = "roc_builtins." ++ fn_name, .linkage = .Strong }); @export(func, .{ .name = "roc_builtins." ++ funcName, .linkage = .Strong });
} }
fn exportNumFn(comptime fn_target: anytype, comptime fn_name: []const u8) void { fn exportNumFn(comptime func: anytype, comptime funcName: []const u8) void {
exportBuiltinFn(fn_target, "num." ++ fn_name); exportBuiltinFn(func, "num." ++ funcName);
} }
fn exportStrFn(comptime fn_target: anytype, comptime fn_name: []const u8) void { fn exportStrFn(comptime func: anytype, comptime funcName: []const u8) void {
exportBuiltinFn(fn_target, "str." ++ fn_name); exportBuiltinFn(func, "str." ++ funcName);
} }
// Run all tests in imported modules // Run all tests in imported modules

File diff suppressed because it is too large Load diff

View file

@ -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_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_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters";
pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with"; 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";

View file

@ -417,12 +417,24 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
top_level_function(vec![str_type(), str_type()], Box::new(bool_type())), 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 // countGraphemes : Str -> Int
add_type( add_type(
Symbol::STR_COUNT_GRAPHEMES, Symbol::STR_COUNT_GRAPHEMES,
top_level_function(vec![str_type()], Box::new(int_type())), 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 // List module
// get : List elem, Int -> Result elem [ OutOfBounds ]* // get : List elem, Int -> Result elem [ OutOfBounds ]*

View file

@ -1096,12 +1096,24 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
unique_function(vec![str_type(star1), str_type(star2)], bool_type(star3)) 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 // Str.countGraphemes : Attr * Str, -> Attr * Int
add_type(Symbol::STR_COUNT_GRAPHEMES, { add_type(Symbol::STR_COUNT_GRAPHEMES, {
let_tvars! { star1, star2 }; let_tvars! { star1, star2 };
unique_function(vec![str_type(star1)], int_type(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 // Result module
// map : Attr * (Result (Attr a e)) // map : Attr * (Result (Attr a e))

View file

@ -54,7 +54,9 @@ pub fn builtin_defs(var_store: &mut VarStore) -> MutMap<Symbol, Def> {
Symbol::STR_SPLIT => str_split, Symbol::STR_SPLIT => str_split,
Symbol::STR_IS_EMPTY => str_is_empty, Symbol::STR_IS_EMPTY => str_is_empty,
Symbol::STR_STARTS_WITH => str_starts_with, Symbol::STR_STARTS_WITH => str_starts_with,
Symbol::STR_ENDS_WITH => str_ends_with,
Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes, Symbol::STR_COUNT_GRAPHEMES => str_count_graphemes,
Symbol::STR_FROM_INT => str_from_int,
Symbol::LIST_LEN => list_len, Symbol::LIST_LEN => list_len,
Symbol::LIST_GET => list_get, Symbol::LIST_GET => list_get,
Symbol::LIST_SET => list_set, 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 /// Str.countGraphemes : Str -> Int
fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def { fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh(); 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 /// List.concat : List elem, List elem -> List elem
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_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, list_reverse, list_set, list_single, list_sum, list_walk, list_walk_backwards,
}; };
use crate::llvm::build_str::{ 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::compare::{build_eq, build_neq};
use crate::llvm::convert::{ use crate::llvm::convert::{
@ -2426,15 +2427,25 @@ fn run_low_level<'a, 'ctx, 'env>(
let inplace = get_inplace_from_layout(layout); 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 => { StrStartsWith => {
// Str.startsWith : Str, Str -> Bool // Str.startsWith : Str, Str -> Bool
debug_assert_eq!(args.len(), 2); 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 => { StrSplit => {
// Str.split : Str, Str -> List Str // Str.split : Str, Str -> List Str
@ -2442,14 +2453,13 @@ fn run_low_level<'a, 'ctx, 'env>(
let inplace = get_inplace_from_layout(layout); 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 => { StrIsEmpty => {
// Str.isEmpty : Str -> Str // Str.isEmpty : Str -> Str
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
let wrapper_ptr = ptr_from_symbol(scope, args[0]); let len = str_number_of_bytes(env, scope, args[0]);
let len = str_len(env, parent, *wrapper_ptr);
let is_zero = env.builder.build_int_compare( let is_zero = env.builder.build_int_compare(
IntPredicate::EQ, IntPredicate::EQ,
len, len,
@ -2462,7 +2472,7 @@ fn run_low_level<'a, 'ctx, 'env>(
// Str.countGraphemes : Str -> Int // Str.countGraphemes : Str -> Int
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
str_count_graphemes(env, scope, parent, args[0]) str_count_graphemes(env, scope, args[0])
} }
ListLen => { ListLen => {
// List.len : List * -> Int // List.len : List * -> Int

View file

@ -3,7 +3,7 @@ use crate::llvm::build::{
}; };
use crate::llvm::compare::build_eq; use crate::llvm::compare::build_eq;
use crate::llvm::convert::{basic_type_from_layout, collection, get_ptr_type}; 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::builder::Builder;
use inkwell::context::Context; use inkwell::context::Context;
use inkwell::types::{BasicTypeEnum, PointerType}; 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 /// List.map : List before, (before -> after) -> List after
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn list_map<'a, 'ctx, 'env>( pub fn list_map<'a, 'ctx, 'env>(
@ -1332,104 +1411,50 @@ pub fn list_map<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
match (func, func_layout) { match (func, func_layout) {
(BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, ret_elem_layout)) => { (BasicValueEnum::PointerValue(func_ptr), Layout::FunctionPointer(_, ret_elem_layout)) => {
let non_empty_fn = |elem_layout: &Layout<'a>, list_map_help!(
len: IntValue<'ctx>, env,
list_wrapper: StructValue<'ctx>| { layout_ids,
let ctx = env.context; inplace,
let builder = env.builder; parent,
func,
let ret_list_ptr = allocate_list(env, inplace, ret_elem_layout, len); func_layout,
list,
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); list_layout,
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); func_ptr,
ret_elem_layout,
let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type); None
)
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")
} }
(BasicValueEnum::StructValue(ptr_and_data), Layout::Closure(_, _, ret_elem_layout)) => { (
let non_empty_fn = |elem_layout: &Layout<'a>, BasicValueEnum::StructValue(ptr_and_data),
len: IntValue<'ctx>, Layout::Closure(_, closure_layout, ret_elem_layout),
list_wrapper: StructValue<'ctx>| { ) => {
let ctx = env.context; let builder = env.builder;
let builder = env.builder;
let func_ptr = builder let func_ptr = builder
.build_extract_value(ptr_and_data, 0, "function_ptr") .build_extract_value(ptr_and_data, 0, "function_ptr")
.unwrap() .unwrap()
.into_pointer_value(); .into_pointer_value();
let closure_data = builder let closure_data = builder
.build_extract_value(ptr_and_data, 1, "closure_data") .build_extract_value(ptr_and_data, 1, "closure_data")
.unwrap(); .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); list_map_help!(
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); env,
layout_ids,
let list_ptr = load_list_ptr(builder, list_wrapper, ptr_type); inplace,
parent,
let list_loop = |index, before_elem| { func,
let call_site_value = builder.build_call( func_layout,
func_ptr, list,
env.arena.alloc([before_elem, closure_data]), list_layout,
"map_func", func_ptr,
); ret_elem_layout,
Some((&closure_data_layout, closure_data))
// 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")
} }
_ => { _ => {
unreachable!( unreachable!(

View file

@ -1,115 +1,65 @@
use crate::llvm::build::{ use crate::llvm::build::{
call_bitcode_fn, call_void_bitcode_fn, ptr_from_symbol, Env, InPlace, Scope, 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 crate::llvm::convert::collection;
use inkwell::builder::Builder;
use inkwell::types::BasicTypeEnum; use inkwell::types::BasicTypeEnum;
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::values::{BasicValueEnum, IntValue, StructValue};
use inkwell::{AddressSpace, IntPredicate}; use inkwell::AddressSpace;
use roc_builtins::bitcode; use roc_builtins::bitcode;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout};
use super::build::load_symbol;
pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8); pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8);
/// Str.split : Str, Str -> List Str /// Str.split : Str, Str -> List Str
pub fn str_split<'a, 'ctx, 'env>( pub fn str_split<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
inplace: InPlace, inplace: InPlace,
str_symbol: Symbol, str_symbol: Symbol,
delimiter_symbol: Symbol, delimiter_symbol: Symbol,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; let builder = env.builder;
let ctx = env.context;
let str_ptr = ptr_from_symbol(scope, str_symbol); let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
let delimiter_ptr = ptr_from_symbol(scope, delimiter_symbol); let delim_i128 = str_symbol_to_i128(env, scope, delimiter_symbol);
let str_wrapper_type = BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)); let segment_count = call_bitcode_fn(
load_str(
env, env,
parent, &[str_i128.into(), delim_i128.into()],
*str_ptr, &bitcode::STR_COUNT_SEGMENTS,
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),
],
&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);
// get the RocStr type defined by zig
let roc_str_type = env.module.get_struct_type("str.RocStr").unwrap();
// convert `*mut { *mut u8, i64 }` to `*mut RocStr`
let ret_list_ptr_zig_rocstr = builder.build_bitcast(
ret_list_ptr,
roc_str_type.ptr_type(AddressSpace::Generic),
"convert_to_zig_rocstr",
);
call_void_bitcode_fn(
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),
],
&bitcode::STR_STR_SPLIT_IN_PLACE,
);
store_list(env, ret_list_ptr, segment_count)
},
)
},
) )
} .into_int_value();
// a pointer to the elements
let ret_list_ptr = allocate_list(env, inplace, &Layout::Builtin(Builtin::Str), 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 // get the RocStr type defined by zig
let roc_str_type = env.module.get_struct_type("str.RocStr").unwrap(); let roc_str_type = env.module.get_struct_type("str.RocStr").unwrap();
// convert `{ *mut u8, i64 }` to `RocStr` // convert `*mut { *mut u8, i64 }` to `*mut RocStr`
builder.build_bitcast(str_as_struct, roc_str_type, "convert_to_zig_rocstr"); let ret_list_ptr_zig_rocstr = builder.build_bitcast(
} ret_list_ptr,
roc_str_type.ptr_type(AddressSpace::Generic),
"convert_to_zig_rocstr",
);
fn cast_from_zig_str( call_void_bitcode_fn(
env: &Env<'a, 'ctx, 'env>, env,
str_as_struct: StructValue<'ctx>, &[
) -> BasicValueEnum<'ctx> { ret_list_ptr_zig_rocstr,
let ret_type = BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)); BasicValueEnum::IntValue(segment_count),
str_i128.into(),
delim_i128.into(),
],
&bitcode::STR_STR_SPLIT_IN_PLACE,
);
// convert `RocStr` to `{ *mut u8, i64 }` store_list(env, ret_list_ptr, segment_count)
builder.build_bitcast(str_as_struct, ret_type, "convert_from_zig_rocstr");
} }
*/
fn str_symbol_to_i128<'a, 'ctx, 'env>( fn str_symbol_to_i128<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
@ -172,7 +122,6 @@ pub fn str_concat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
inplace: InPlace, inplace: InPlace,
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
_parent: FunctionValue<'ctx>,
str1_symbol: Symbol, str1_symbol: Symbol,
str2_symbol: Symbol, str2_symbol: Symbol,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
@ -201,223 +150,53 @@ pub fn str_concat<'a, 'ctx, 'env>(
zig_str_to_struct(env, zig_result).into() zig_str_to_struct(env, zig_result).into()
} }
/// Obtain the string's length, cast from i8 to usize pub fn str_number_of_bytes<'a, 'ctx, 'env>(
fn str_len_from_final_byte<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
final_byte: IntValue<'ctx>, scope: &Scope<'a, 'ctx>,
str_symbol: Symbol,
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let builder = env.builder; let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
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");
builder.build_int_cast(len_i8, env.ptr_int(), "len_as_usize") // the builtin will always return an u64
} let length =
call_bitcode_fn(env, &[str_i128.into()], &bitcode::STR_NUMBER_OF_BYTES).into_int_value();
/// 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);
// cast to the appropriate usize of the current build
env.builder env.builder
.build_bitcast(wrapper_ptr, array_ptr_type, "str_as_array_ptr") .build_int_cast(length, env.ptr_int(), "len_as_usize")
.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",
)
} }
/// Str.startsWith : Str, Str -> Bool /// Str.startsWith : Str, Str -> Bool
pub fn str_starts_with<'a, 'ctx, 'env>( pub fn str_starts_with<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
_inplace: InPlace,
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
str_symbol: Symbol, str_symbol: Symbol,
prefix_symbol: Symbol, prefix_symbol: Symbol,
) -> BasicValueEnum<'ctx> { ) -> 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); call_bitcode_fn(
let prefix_ptr = ptr_from_symbol(scope, prefix_symbol);
let ret_type = BasicTypeEnum::IntType(ctx.bool_type());
load_str(
env, env,
parent, &[str_i128.into(), prefix_i128.into()],
*str_ptr, &bitcode::STR_STARTS_WITH,
ret_type, )
|str_bytes_ptr, str_len, _str_smallness| { }
load_str(
env, /// Str.endsWith : Str, Str -> Bool
parent, pub fn str_ends_with<'a, 'ctx, 'env>(
*prefix_ptr, env: &Env<'a, 'ctx, 'env>,
ret_type, scope: &Scope<'a, 'ctx>,
|prefix_bytes_ptr, prefix_len, _prefix_smallness| { str_symbol: Symbol,
call_bitcode_fn( prefix_symbol: Symbol,
env, ) -> BasicValueEnum<'ctx> {
&[ let str_i128 = str_symbol_to_i128(env, scope, str_symbol);
BasicValueEnum::PointerValue(str_bytes_ptr), let prefix_i128 = str_symbol_to_i128(env, scope, prefix_symbol);
BasicValueEnum::IntValue(str_len),
BasicValueEnum::PointerValue(prefix_bytes_ptr), call_bitcode_fn(
BasicValueEnum::IntValue(prefix_len), env,
], &[str_i128.into(), prefix_i128.into()],
&bitcode::STR_STARTS_WITH, &bitcode::STR_ENDS_WITH,
)
},
)
},
) )
} }
@ -425,28 +204,26 @@ pub fn str_starts_with<'a, 'ctx, 'env>(
pub fn str_count_graphemes<'a, 'ctx, 'env>( pub fn str_count_graphemes<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>, scope: &Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
str_symbol: Symbol, str_symbol: Symbol,
) -> BasicValueEnum<'ctx> { ) -> 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); call_bitcode_fn(
let ret_type = BasicTypeEnum::IntType(ctx.i64_type());
load_str(
env, env,
parent, &[str_i128.into()],
*sym_str_ptr, &bitcode::STR_COUNT_GRAPEHEME_CLUSTERS,
ret_type,
|str_ptr, str_len, _str_smallness| {
call_bitcode_fn(
env,
&[
BasicValueEnum::PointerValue(str_ptr),
BasicValueEnum::IntValue(str_len),
],
&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()
}

View file

@ -2,7 +2,7 @@ use crate::llvm::build::{
cast_basic_basic, cast_struct_struct, create_entry_block_alloca, set_name, Env, Scope, cast_basic_basic, cast_struct_struct, create_entry_block_alloca, set_name, Env, Scope,
FAST_CALL_CONV, LLVM_SADD_WITH_OVERFLOW_I64, 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 crate::llvm::convert::{basic_type_from_layout, block_of_memory, ptr_int};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use inkwell::context::Context; use inkwell::context::Context;
@ -367,7 +367,6 @@ fn decrement_refcount_builtin<'a, 'ctx, 'env>(
List(memory_mode, element_layout) => { List(memory_mode, element_layout) => {
let wrapper_struct = value.into_struct_value(); let wrapper_struct = value.into_struct_value();
if element_layout.contains_refcounted() { if element_layout.contains_refcounted() {
use crate::llvm::build_list::{incrementing_elem_loop, load_list};
use inkwell::types::BasicType; use inkwell::types::BasicType;
let ptr_type = let ptr_type =
@ -451,7 +450,6 @@ fn increment_refcount_builtin<'a, 'ctx, 'env>(
List(memory_mode, element_layout) => { List(memory_mode, element_layout) => {
let wrapper_struct = value.into_struct_value(); let wrapper_struct = value.into_struct_value();
if element_layout.contains_refcounted() { if element_layout.contains_refcounted() {
use crate::llvm::build_list::{incrementing_elem_loop, load_list};
use inkwell::types::BasicType; use inkwell::types::BasicType;
let ptr_type = let ptr_type =

View file

@ -433,6 +433,13 @@ mod gen_str {
assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool); 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] #[test]
fn str_count_graphemes_small_str() { fn str_count_graphemes_small_str() {
assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize); assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize);
@ -483,4 +490,29 @@ mod gen_str {
fn str_starts_with_false_small_str() { fn str_starts_with_false_small_str() {
assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool); 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);
}
} }

View file

@ -6,8 +6,10 @@ pub enum LowLevel {
StrConcat, StrConcat,
StrIsEmpty, StrIsEmpty,
StrStartsWith, StrStartsWith,
StrEndsWith,
StrSplit, StrSplit,
StrCountGraphemes, StrCountGraphemes,
StrFromInt,
ListLen, ListLen,
ListGetUnsafe, ListGetUnsafe,
ListSet, ListSet,

View file

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

View file

@ -547,6 +547,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
| NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin => { | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin => {
arena.alloc_slice_copy(&[irrelevant]) 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]),
} }
} }

View file

@ -460,7 +460,7 @@ impl<'a> Layout<'a> {
pub fn is_refcounted(&self) -> bool { pub fn is_refcounted(&self) -> bool {
match self { match self {
Layout::Builtin(Builtin::List(_, _)) => true, Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) => true,
Layout::Builtin(Builtin::Str) => true, Layout::Builtin(Builtin::Str) => true,
Layout::RecursiveUnion(_) => true, Layout::RecursiveUnion(_) => true,
Layout::RecursivePointer => true, Layout::RecursivePointer => true,
@ -477,12 +477,12 @@ impl<'a> Layout<'a> {
match self { match self {
Builtin(builtin) => builtin.is_refcounted(), Builtin(builtin) => builtin.is_refcounted(),
PhantomEmptyStruct => false, PhantomEmptyStruct => false,
Struct(fields) => fields.iter().any(|f| f.is_refcounted()), Struct(fields) => fields.iter().any(|f| f.contains_refcounted()),
Union(fields) => fields Union(fields) => fields
.iter() .iter()
.map(|ls| ls.iter()) .map(|ls| ls.iter())
.flatten() .flatten()
.any(|f| f.is_refcounted()), .any(|f| f.contains_refcounted()),
RecursiveUnion(_) => true, RecursiveUnion(_) => true,
Closure(_, closure_layout, _) => closure_layout.contains_refcounted(), Closure(_, closure_layout, _) => closure_layout.contains_refcounted(),
FunctionPointer(_, _) | RecursivePointer | Pointer(_) => false, FunctionPointer(_, _) | RecursivePointer | Pointer(_) => false,

View file

@ -917,9 +917,13 @@ fn parse_def_signature<'a>(
fn loc_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Expr<'a>>> { fn loc_function_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<Expr<'a>>> {
skip_first!( skip_first!(
// If this is a reserved keyword ("if", "then", "case, "when"), then // If this is a reserved keyword ("if", "then", "case, "when"),
// it is not a function argument! // followed by a blank space, then it is not a function argument!
not(reserved_keyword()), //
// (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. // Don't parse operators, because they have a higher precedence than function application.
// If we encounter one, we're done parsing function args! // If we encounter one, we're done parsing function args!
move |arena, state| loc_parse_function_arg(min_indent, arena, state) move |arena, state| loc_parse_function_arg(min_indent, arena, state)

View file

@ -777,24 +777,25 @@ macro_rules! skip_first {
use $crate::parser::Fail; use $crate::parser::Fail;
let original_attempting = state.attempting; let original_attempting = state.attempting;
let original_state = state.clone();
match $p1.parse(arena, state) { match $p1.parse(arena, state) {
Ok((_, state)) => match $p2.parse(arena, state) { Ok((_, state)) => match $p2.parse(arena, state) {
Ok((out2, state)) => Ok((out2, state)), Ok((out2, state)) => Ok((out2, state)),
Err((fail, state)) => Err(( Err((fail, _)) => Err((
Fail { Fail {
attempting: original_attempting, attempting: original_attempting,
..fail ..fail
}, },
state, original_state,
)), )),
}, },
Err((fail, state)) => Err(( Err((fail, _)) => Err((
Fail { Fail {
attempting: original_attempting, attempting: original_attempting,
..fail ..fail
}, },
state, original_state,
)), )),
} }
} }
@ -810,24 +811,25 @@ macro_rules! skip_second {
use $crate::parser::Fail; use $crate::parser::Fail;
let original_attempting = state.attempting; let original_attempting = state.attempting;
let original_state = state.clone();
match $p1.parse(arena, state) { match $p1.parse(arena, state) {
Ok((out1, state)) => match $p2.parse(arena, state) { Ok((out1, state)) => match $p2.parse(arena, state) {
Ok((_, state)) => Ok((out1, state)), Ok((_, state)) => Ok((out1, state)),
Err((fail, state)) => Err(( Err((fail, _)) => Err((
Fail { Fail {
attempting: original_attempting, attempting: original_attempting,
..fail ..fail
}, },
state, original_state,
)), )),
}, },
Err((fail, state)) => Err(( Err((fail, _)) => Err((
Fail { Fail {
attempting: original_attempting, attempting: original_attempting,
..fail ..fail
}, },
state, original_state,
)), )),
} }
} }

View file

@ -814,6 +814,71 @@ mod test_parse {
assert_eq!(Ok(expected), actual); 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] #[test]
fn parenthetical_var() { fn parenthetical_var() {
let arena = Bump::new(); 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] #[test]
fn one_spaced_def() { fn one_spaced_def() {
let arena = Bump::new(); let arena = Bump::new();
@ -2573,6 +2664,41 @@ mod test_parse {
assert_eq!(Ok(expected), actual); 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] #[test]
fn standalone_module_defs() { fn standalone_module_defs() {
use roc_parse::ast::Def::*; use roc_parse::ast::Def::*;

View file

@ -4,7 +4,7 @@ app "effect-example" imports [ Effect ] provides [ main ] to "./platform"
main : Effect.Effect {} as Fx main : Effect.Effect {} as Fx
main = main =
when if 1 == 1 then True 3 else False 3.14 is 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" _ -> Effect.putLine "Yay"
# main : Effect.Effect {} as Fx # main : Effect.Effect {} as Fx