sort: fix sorting by struct fields

pull/6117/head
Alexander Medvednikov 2020-08-12 18:43:41 +02:00
parent e5e31f7210
commit f8be2110ec
3 changed files with 69 additions and 15 deletions

View File

@ -736,6 +736,9 @@ fn test_sort() {
assert(users[0].age == 25) assert(users[0].age == 25)
assert(users[1].age == 22) assert(users[1].age == 22)
assert(users[2].age == 20) assert(users[2].age == 20)
//
users.sort(a.name < b.name) // Test sorting by string fields
//assert users.map(it.name).join(' ') == 'Alice Bob Peter'
} }
fn test_f32_sort() { fn test_f32_sort() {

View File

@ -183,3 +183,10 @@ fn (flags []Flag) contains(name string) bool {
return false return false
} }
fn (mut flags []Flag) sort2() {
flags.sort_with_compare(fn (a, b &Flag) int {
return compare_strings(&a.name, &b.name)
})
}

View File

@ -24,10 +24,10 @@ const (
) )
struct Gen { struct Gen {
table &table.Table
pref &pref.Preferences pref &pref.Preferences
module_built string module_built string
mut: mut:
table &table.Table
out strings.Builder out strings.Builder
cheaders strings.Builder cheaders strings.Builder
includes strings.Builder // all C #includes required by V modules includes strings.Builder // all C #includes required by V modules
@ -3739,14 +3739,14 @@ fn (mut g Gen) gen_array_map(node ast.CallExpr) {
// `users.sort(a.age < b.age)` // `users.sort(a.age < b.age)`
fn (mut g Gen) gen_array_sort(node ast.CallExpr) { fn (mut g Gen) gen_array_sort(node ast.CallExpr) {
// println('filter s="$s"') // println('filter s="$s"')
sym := g.table.get_type_symbol(node.return_type) return_sym := g.table.get_type_symbol(node.return_type)
if sym.kind != .array { if return_sym.kind != .array {
println(node.name) println(node.name)
println(g.typ(node.receiver_type)) println(g.typ(node.receiver_type))
println(sym.kind) // println(sym.kind)
verror('sort() requires an array') verror('sort() requires an array')
} }
info := sym.info as table.Array info := return_sym.info as table.Array
// No arguments means we are sorting an array of builtins (e.g. `numbers.sort()`) // No arguments means we are sorting an array of builtins (e.g. `numbers.sort()`)
// The type for the comparison fns is the type of the element itself. // The type for the comparison fns is the type of the element itself.
mut typ := info.elem_type mut typ := info.elem_type
@ -3756,31 +3756,75 @@ fn (mut g Gen) gen_array_sort(node ast.CallExpr) {
// Get the type of the field that's being compared // Get the type of the field that's being compared
// `a.age > b.age` => `age int` => int // `a.age > b.age` => `age int` => int
infix_expr := node.args[0].expr as ast.InfixExpr infix_expr := node.args[0].expr as ast.InfixExpr
typ = infix_expr.left_type // typ = infix_expr.left_type
is_reverse = infix_expr.op == .gt is_reverse = infix_expr.op == .gt
} }
mut compare_fn := match typ { mut compare_fn := ''
match typ {
table.int_type { table.int_type {
'compare_ints' compare_fn = 'compare_ints'
} }
table.string_type { table.string_type {
'compare_strings' compare_fn = 'compare_strings'
} }
table.f64_type { table.f64_type {
'compare_floats' compare_fn = 'compare_floats'
} }
else { else {
q := g.table.get_type_symbol(typ) // Generate a comparison function for a custom type
if node.args.len == 0 { if node.args.len == 0 {
verror('usage: .sort(a.field < b.field) $q.name') verror('usage: .sort(a.field < b.field)')
}
verror('sort(): unhandled type $typ $q.name')
''
}
} }
// verror('sort(): unhandled type $typ $q.name')
compare_fn = 'compare_' + g.typ(typ)
if is_reverse { if is_reverse {
compare_fn += '_reverse' compare_fn += '_reverse'
} }
if _ := g.table.find_fn(compare_fn) {
} else {
// Register a new custom `compare_xxx` function for qsort()
g.table.register_fn({
name: compare_fn
return_type: table.int_type
})
infix_expr := node.args[0].expr as ast.InfixExpr
styp := g.typ(typ)
// Variables `a` and `b` are used in the `.sort(a < b)` syntax, so we can reuse them
// when generating the function as long as the args are named the same.
g.definitions.writeln('int $compare_fn ($styp* a, $styp* b) {')
field_type := g.typ(infix_expr.left_type)
left_expr_str := g.write_expr_to_string(infix_expr.left).replace('.',
'->')
right_expr_str := g.write_expr_to_string(infix_expr.right).replace('.',
'->')
g.definitions.writeln('$field_type a_ = $left_expr_str;')
g.definitions.writeln('$field_type b_ = $right_expr_str;')
mut op1, mut op2 := '', ''
if infix_expr.left_type == table.string_type {
if is_reverse {
op1 = 'string_gt(a_, b_)'
op2 = 'string_lt(a_, b_)'
} else {
op1 = 'string_lt(a_, b_)'
op2 = 'string_gt(a_, b_)'
}
} else {
if is_reverse {
op1 = 'a_ > b_'
op2 = 'a_ < b_'
} else {
op1 = 'a_ < b_'
op2 = 'a_ > b_'
}
}
g.definitions.writeln('if ($op1) return -1;')
g.definitions.writeln('if ($op2) return 1; return 0; }\n')
}
}
}
if is_reverse && !compare_fn.ends_with('_reverse') {
compare_fn += '_reverse'
}
// //
g.write('qsort(') g.write('qsort(')
g.expr(node.left) g.expr(node.left)