diff --git a/vlib/builtin/fixed_array_sort_test.v b/vlib/builtin/fixed_array_sort_test.v index 6771946795..c5a1052250 100644 --- a/vlib/builtin/fixed_array_sort_test.v +++ b/vlib/builtin/fixed_array_sort_test.v @@ -69,3 +69,98 @@ fn test_sorted_by_len() { c := a.sorted(a.len < b.len) assert c == ['a', 'hi', 'abc', 'zzzzz']! } + +fn test_sort_with_compare_1() { + mut a := ['hi', '1', '5', '3']! + a.sort_with_compare(fn (a &string, b &string) int { + if a < b { + return -1 + } + if a > b { + return 1 + } + return 0 + }) + assert a == ['1', '3', '5', 'hi']! +} + +fn test_sorted_with_compare_1() { + a := ['hi', '1', '5', '3']! + b := a.sorted_with_compare(fn (a &string, b &string) int { + if a < b { + return -1 + } + if a > b { + return 1 + } + return 0 + }) + assert a == ['hi', '1', '5', '3']! + assert b == ['1', '3', '5', 'hi']! +} + +struct Ka { + s string + i int +} + +fn test_sort_with_compare_2() { + mut arr := [ + Ka{ + s: 'bbb' + i: 100 + }, + Ka{ + s: 'aaa' + i: 101 + }, + Ka{ + s: 'ccc' + i: 102 + }, + ]! + cmp := fn (a &Ka, b &Ka) int { + return compare_strings(a.s, b.s) + } + arr.sort_with_compare(cmp) + assert arr[0].s == 'aaa' + assert arr[0].i == 101 + assert arr[1].s == 'bbb' + assert arr[1].i == 100 + assert arr[2].s == 'ccc' + assert arr[2].i == 102 +} + +fn test_sorted_with_compare_2() { + arr := [ + Ka{ + s: 'bbb' + i: 100 + }, + Ka{ + s: 'aaa' + i: 101 + }, + Ka{ + s: 'ccc' + i: 102 + }, + ]! + cmp := fn (a &Ka, b &Ka) int { + return compare_strings(a.s, b.s) + } + b := arr.sorted_with_compare(cmp) + assert arr[0].s == 'bbb' + assert arr[0].i == 100 + assert arr[1].s == 'aaa' + assert arr[1].i == 101 + assert arr[2].s == 'ccc' + assert arr[2].i == 102 + + assert b[0].s == 'aaa' + assert b[0].i == 101 + assert b[1].s == 'bbb' + assert b[1].i == 100 + assert b[2].s == 'ccc' + assert b[2].i == 102 +} diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index f77d6a53fd..c68238a663 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -33,7 +33,7 @@ pub const array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map' 'first', 'last', 'pop', 'delete', 'insert', 'prepend'] pub const array_builtin_methods_chk = token.new_keywords_matcher_from_array_trie(array_builtin_methods) pub const fixed_array_builtin_methods = ['contains', 'index', 'any', 'all', 'wait', 'map', 'sort', - 'sorted'] + 'sorted', 'sort_with_compare', 'sorted_with_compare'] pub const fixed_array_builtin_methods_chk = token.new_keywords_matcher_from_array_trie(fixed_array_builtin_methods) // TODO: remove `byte` from this list when it is no longer supported pub const reserved_type_names = ['byte', 'bool', 'char', 'i8', 'i16', 'int', 'i64', 'u8', 'u16', diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 617d6b5a31..69f1483411 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -3622,6 +3622,52 @@ fn (mut c Checker) fixed_array_builtin_method_call(mut node ast.CallExpr, left_t } else { node.return_type = node.left_type } + } else if method_name in ['sort_with_compare', 'sorted_with_compare'] { + if node.args.len != 1 { + c.error('`.${method_name}()` expected 1 argument, but got ${node.args.len}', + node.pos) + } else { + if mut node.args[0].expr is ast.LambdaExpr { + c.support_lambda_expr_in_sort(elem_typ.ref(), ast.int_type, mut node.args[0].expr) + } + arg_type := c.expr(mut node.args[0].expr) + arg_sym := c.table.sym(arg_type) + if arg_sym.kind == .function { + func_info := arg_sym.info as ast.FnType + if func_info.func.params.len == 2 { + if func_info.func.params[0].typ.nr_muls() != elem_typ.nr_muls() + 1 { + arg_typ_str := c.table.type_to_str(func_info.func.params[0].typ) + expected_typ_str := c.table.type_to_str(elem_typ.ref()) + c.error('${method_name} callback function parameter `${func_info.func.params[0].name}` with type `${arg_typ_str}` should be `${expected_typ_str}`', + func_info.func.params[0].type_pos) + } + if func_info.func.params[1].typ.nr_muls() != elem_typ.nr_muls() + 1 { + arg_typ_str := c.table.type_to_str(func_info.func.params[1].typ) + expected_typ_str := c.table.type_to_str(elem_typ.ref()) + c.error('${method_name} callback function parameter `${func_info.func.params[1].name}` with type `${arg_typ_str}` should be `${expected_typ_str}`', + func_info.func.params[1].type_pos) + } + } + } + node.args[0].typ = arg_type + if method := c.table.find_method(left_sym, method_name) { + c.check_expected_call_arg(arg_type, method.params[1].typ, node.language, + node.args[0]) or { + c.error('${err.msg()} in argument 1 to `${left_sym.name}.${method_name}`', + node.args[0].pos) + } + } + for mut arg in node.args { + c.check_expr_option_or_result_call(arg.expr, c.expr(mut arg.expr)) + } + if method_name == 'sort_with_compare' { + node.return_type = ast.void_type + node.receiver_type = node.left_type.ref() + } else { + node.return_type = node.left_type + node.receiver_type = node.left_type + } + } } return node.return_type } diff --git a/vlib/v/gen/c/array.v b/vlib/v/gen/c/array.v index 76ca955bf8..cacd3e2cc6 100644 --- a/vlib/v/gen/c/array.v +++ b/vlib/v/gen/c/array.v @@ -776,17 +776,56 @@ fn (mut g Gen) gen_array_sort_call(node ast.CallExpr, compare_fn string, is_arra g.expr(node.left) g.write('${deref_field}len, ') g.expr(node.left) - g.write2('${deref_field}element_size, (int (*)(const void *, const void *))&${compare_fn});', - ' }') + g.write2('${deref_field}element_size, (voidptr)${compare_fn});', ' }') } else { info := g.table.final_sym(node.left_type).info as ast.ArrayFixed elem_styp := g.styp(info.elem_type) g.write('qsort(&') g.expr(node.left) - g.write(', ${info.size}, sizeof(${elem_styp}), (int (*)(const void *, const void *))&${compare_fn});') + g.write(', ${info.size}, sizeof(${elem_styp}), (voidptr)${compare_fn});') } } +fn (mut g Gen) gen_fixed_array_sorted_with_compare(node ast.CallExpr) { + past := g.past_tmp_var_new() + defer { + g.past_tmp_var_done(past) + } + atype := g.styp(node.return_type) + g.writeln('${atype} ${past.tmp_var};') + g.write('memcpy(&${past.tmp_var}, &') + g.expr(node.left) + g.writeln(', sizeof(${atype}));') + + unsafe { + node.left = ast.Expr(ast.Ident{ + name: past.tmp_var + }) + } + g.gen_fixed_array_sort_with_compare(node) + g.writeln(';') +} + +fn (mut g Gen) gen_fixed_array_sort_with_compare(node ast.CallExpr) { + mut compare_fn := '' + if node.args[0].expr is ast.LambdaExpr { + lambda_fn_name := node.args[0].expr.func.decl.name + compare_fn = '${lambda_fn_name}_lambda_wrapper' + mut lambda_node := unsafe { node.args[0].expr } + g.gen_anon_fn_decl(mut lambda_node.func) + } else if node.args[0].expr is ast.AnonFn { + mut fn_expr := unsafe { node.args[0].expr } + g.gen_anon_fn_decl(mut fn_expr) + compare_fn = node.args[0].expr.decl.name + } else if node.args[0].expr is ast.Ident { + compare_fn = node.args[0].expr.name + } else { + compare_fn = node.args[0].expr.str() + } + // write call to the generated function + g.gen_array_sort_call(node, compare_fn, false) +} + // `nums.filter(it % 2 == 0)` fn (mut g Gen) gen_array_filter(node ast.CallExpr) { past := g.past_tmp_var_new() diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index b365a44626..f86ab3fea1 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1210,6 +1210,12 @@ fn (mut g Gen) gen_fixed_array_method_call(node ast.CallExpr, left_type ast.Type 'sorted' { g.gen_array_sorted(node) } + 'sort_with_compare' { + g.gen_fixed_array_sort_with_compare(node) + } + 'sorted_with_compare' { + g.gen_fixed_array_sorted_with_compare(node) + } else { return false }