Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cranelift/runtime-abi/runtime_functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pith_list_push|pith_list_push|I64,I64,I64|
pith_list_push_value|pith_list_push_value|I64,I64|
pith_list_set_value|pith_list_set_value|I64,I64,I64|
list_join|pith_list_join|I64,I64|I64
list_join_int|pith_list_join_int|I64,I64|I64
pith_list_get_value|pith_list_get_value|I64,I64|I64
pith_list_get_value_unchecked|pith_list_get_value_unchecked|I64,I64|I64
pith_list_len|pith_list_len|I64|I64
Expand Down
31 changes: 31 additions & 0 deletions cranelift/runtime/src/collections/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,37 @@ pub unsafe extern "C" fn pith_list_join(list: PithList, sep: *const i8) -> *mut
out
}

/// Join a list of Int elements with a separator, formatting each as decimal.
/// Unlike `pith_list_join`, the elements are values rather than string pointers.
/// Returns a newly allocated C string.
#[no_mangle]
pub unsafe extern "C" fn pith_list_join_int(list: PithList, sep: *const i8) -> *mut i8 {
let Some(impl_ref) = list_ref(list) else {
return std::ptr::null_mut();
};

let sep_str = if sep.is_null() {
""
} else {
let len = crate::string::pith_cstring_len(sep) as usize;
std::str::from_utf8(std::slice::from_raw_parts(sep as *const u8, len)).unwrap_or("")
};

let mut out = String::new();
let mut i = 0usize;
while i < impl_ref.len() {
if let Some(raw) = impl_ref.get_value(i) {
if i > 0 {
out.push_str(sep_str);
}
out.push_str(&raw.to_string());
}
i += 1;
}

crate::pith_copy_bytes_to_cstring(out.as_bytes())
}

/// Pop element from end of list
///
/// Returns true if successful, false if list is empty
Expand Down
4 changes: 4 additions & 0 deletions self-host/checker.pith
Original file line number Diff line number Diff line change
Expand Up @@ -3001,6 +3001,10 @@ fn check_list_method_call(method: String, info: TypeInfo, args: List[Int], scope
return tid_bool
if method == "join":
expect_one_typed_argument(method, args, tid_string, scope_id)
# join formats elements into a string; only String and Int elements are
# supported. other element types would otherwise crash at runtime.
if elem != tid_string and is_integer_type(elem) == false:
report_error("E219", "join requires a list of String or Int elements, got List[" + get_type_name(elem) + "]; map elements to String first")
return tid_string
if method == "index_of":
expect_one_typed_argument(method, args, elem, scope_id)
Expand Down
Binary file modified self-host/ir_driver
Binary file not shown.
3 changes: 3 additions & 0 deletions self-host/ir_emitter_core.pith
Original file line number Diff line number Diff line change
Expand Up @@ -3288,6 +3288,9 @@ fn ir_emit_method_call(idx: Int) -> Int:
if self_hosted_list_r >= 0:
return self_hosted_list_r
mut emit_name := ir_method_emit_name(mname, obj_type)
if emit_name == "list_join" and ir_list_element_kind(node.children[0]) == "int":
# list_join assumes string elements; Int lists need decimal formatting
emit_name = "list_join_int"
if ir_is_bool_returning_method(emit_name):
ir_var_types.insert("__last_call", "bool")

Expand Down
2 changes: 1 addition & 1 deletion self-host/ir_metadata.pith
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ LIST_RETURNING_FUNCS := "args,range,repeat_val,zeros,sort,sort_desc,reversed,tak
LIST_STRING_RETURNING_FUNCS := "sort_strings,zip,enumerate,toml_keys"
BOOL_RETURNING_METHODS := "contains,starts_with,ends_with,is_empty,string_contains,string_is_empty,contains_key,map_contains_key,map_contains_ikey,map_is_empty,set_contains,set_contains_int,set_is_empty,send,try_send,close,is_closed,is_done"
VOID_METHODS := "reverse,clear,push,remove,insert,add,list_remove,list_clear,list_reverse,map_insert,map_insert_ikey,map_remove,map_remove_ikey,map_clear,set_add,set_add_int,set_remove,set_remove_int,set_clear,detach,lock,unlock,done,wait,acquire,release"
STRING_RETURNING_METHODS := "trim,substring,to_upper,to_lower,reverse,replace,repeat,pad_left,pad_right,char_at,join,list_join,read_file,env,chr"
STRING_RETURNING_METHODS := "trim,substring,to_upper,to_lower,reverse,replace,repeat,pad_left,pad_right,char_at,join,list_join,list_join_int,read_file,env,chr"

fn ir_csv_contains(csv: String, name: String) -> Bool:
if name.len() == 0:
Expand Down
20 changes: 20 additions & 0 deletions tests/cases/test_list_join_int.pith
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# joining Int lists formats each element as decimal; String lists unchanged

fn main():
nums := [1, 2, 3, 4, 5]
print(nums.join(","))
print(nums.join(" -> "))

empty: List[Int] := []
print("empty:[" + empty.join(",") + "]")

print([42].join(","))

# negative numbers
print([0 - 1, 0, 2].join(","))

# combines with map
print(nums.map(fn(x: Int) => x * 2).join("|"))

# String lists still join as before
print(["a", "b", "c"].join("-"))
7 changes: 7 additions & 0 deletions tests/expected/test_list_join_int.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
1,2,3,4,5
1 -> 2 -> 3 -> 4 -> 5
empty:[]
42
-1,0,2
2|4|6|8|10
a-b-c
1 change: 1 addition & 0 deletions tests/invalid/expected/invalid_join_non_string.codes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
E219
3 changes: 3 additions & 0 deletions tests/invalid/invalid_join_non_string.pith
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main():
xs := [1.0, 2.0, 3.0]
print(xs.join(","))
Loading