Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v: add array.count #23054

Merged
merged 7 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
7 changes: 7 additions & 0 deletions vlib/builtin/array.v
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,13 @@ pub fn (a array) filter(predicate fn (voidptr) bool) array
// Example: array.any(it.name == 'Bob') // will yield `true` if any element has `.name == 'Bob'`
pub fn (a array) any(predicate fn (voidptr) bool) bool

// count counts how many elements in array pass the test.
// Ignore the function signature. `count` does not take an actual callback. Rather, it
// takes an `it` expression.
//
// Example: array.count(it % 2 == 1) // will return how many elements are odd
pub fn (a array) count(predicate fn (voidptr) bool) bool
spytheman marked this conversation as resolved.
Show resolved Hide resolved

// all tests whether all elements in the array pass the test.
// Ignore the function signature. `all` does not take an actual callback. Rather, it
// takes an `it` expression.
Expand Down
4 changes: 2 additions & 2 deletions vlib/v/checker/checker.v
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ const generic_fn_postprocess_iterations_cutoff_limit = 1_000_000
// Note that methods that do not return anything, or that return known types, are not listed here, since they are just ordinary non generic methods.
pub const array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort',
'sort_with_compare', 'sorted', 'sorted_with_compare', 'contains', 'index', 'wait', 'any', 'all',
'first', 'last', 'pop', 'delete', 'insert', 'prepend']
'first', 'last', 'pop', 'delete', 'insert', 'prepend', 'count']
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', 'sort_with_compare', 'sorted_with_compare', 'reverse', 'reverse_in_place']
'sorted', 'sort_with_compare', 'sorted_with_compare', 'reverse', 'reverse_in_place', 'count']
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',
Expand Down
25 changes: 24 additions & 1 deletion vlib/v/checker/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -3327,7 +3327,7 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
c.table.sym(unaliased_left_type).info as ast.Array
}
elem_typ = array_info.elem_type
if method_name in ['filter', 'map', 'any', 'all'] {
if method_name in ['filter', 'map', 'any', 'all', 'count'] {
if node.args.len > 0 && mut node.args[0].expr is ast.LambdaExpr {
if node.args[0].expr.params.len != 1 {
c.error('lambda expressions used in the builtin array methods require exactly 1 parameter',
Expand Down Expand Up @@ -3546,6 +3546,9 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
} else if method_name in ['any', 'all'] {
c.check_map_and_filter(false, elem_typ, node)
node.return_type = ast.bool_type
} else if method_name == 'count' {
c.check_map_and_filter(false, elem_typ, node)
spytheman marked this conversation as resolved.
Show resolved Hide resolved
node.return_type = ast.int_type
} else if method_name == 'clone' {
if node.args.len != 0 {
c.error('`.clone()` does not have any arguments', node.args[0].pos)
Expand Down Expand Up @@ -3717,6 +3720,26 @@ fn (mut c Checker) fixed_array_builtin_method_call(mut node ast.CallExpr, left_t
c.expr(mut node.args[0].expr)
c.check_map_and_filter(false, elem_typ, node)
node.return_type = ast.bool_type
} else if method_name == 'count' {
if node.args.len != 1 {
c.error('`.${method_name}` expected 1 argument, but got ${node.args.len}',
node.pos)
return ast.bool_type
}
if node.args.len > 0 && mut node.args[0].expr is ast.LambdaExpr {
if node.args[0].expr.params.len != 1 {
c.error('lambda expressions used in the builtin array methods require exactly 1 parameter',
node.args[0].expr.pos)
return ast.bool_type
}
c.support_lambda_expr_one_param(elem_typ, ast.bool_type, mut node.args[0].expr)
} else {
// position of `it` doesn't matter
scope_register_it(mut node.scope, node.pos, elem_typ)
}
c.expr(mut node.args[0].expr)
c.check_map_and_filter(false, elem_typ, node)
node.return_type = ast.int_type
} else if method_name == 'wait' {
elem_sym := c.table.sym(elem_typ)
if elem_sym.kind == .thread {
Expand Down
20 changes: 20 additions & 0 deletions vlib/v/checker/tests/array_count_err.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
vlib/v/checker/tests/array_count_err.vv:4:4: error: expected 1 argument, but got 2
2 | a := []int{}
3 | a.count(1)
4 | a.count(1, 2)
| ~~~~~~~~~~~
5 | a.count('')
6 | a.count()
vlib/v/checker/tests/array_count_err.vv:5:10: error: type mismatch, should use e.g. `count(it > 2)`
3 | a.count(1)
4 | a.count(1, 2)
5 | a.count('')
| ~~
6 | a.count()
7 | }
vlib/v/checker/tests/array_count_err.vv:6:4: error: expected 1 argument, but got 0
4 | a.count(1, 2)
5 | a.count('')
6 | a.count()
| ~~~~~~~
7 | }
7 changes: 7 additions & 0 deletions vlib/v/checker/tests/array_count_err.vv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn main() {
a := []int{}
a.count(1)
a.count(1, 2)
a.count('')
a.count()
}
98 changes: 94 additions & 4 deletions vlib/v/gen/c/array.v
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ fn (mut g Gen) gen_array_map(node ast.CallExpr) {
}
}
ast.CallExpr {
if expr.name in ['map', 'filter', 'all', 'any'] {
if expr.name in ['map', 'filter', 'all', 'any', 'count'] {
is_embed_map_filter = true
g.set_current_pos_as_last_stmt_pos()
}
Expand Down Expand Up @@ -940,7 +940,7 @@ fn (mut g Gen) gen_array_filter(node ast.CallExpr) {
}
}
ast.CallExpr {
if expr.name in ['map', 'filter', 'all', 'any'] {
if expr.name in ['map', 'filter', 'all', 'any', 'count'] {
is_embed_map_filter = true
g.set_current_pos_as_last_stmt_pos()
}
Expand Down Expand Up @@ -1439,7 +1439,7 @@ fn (mut g Gen) gen_array_any(node ast.CallExpr) {
}
}
ast.CallExpr {
if expr.name in ['map', 'filter', 'all', 'any'] {
if expr.name in ['map', 'filter', 'all', 'any', 'count'] {
is_embed_map_filter = true
g.set_current_pos_as_last_stmt_pos()
}
Expand Down Expand Up @@ -1469,6 +1469,96 @@ fn (mut g Gen) gen_array_any(node ast.CallExpr) {
}
}

fn (mut g Gen) gen_array_count(node ast.CallExpr) {
past := g.past_tmp_var_new()
defer {
g.past_tmp_var_done(past)
}

sym := g.table.final_sym(node.left_type)
left_is_array := sym.kind == .array
elem_type := if left_is_array {
(sym.info as ast.Array).elem_type
} else {
(sym.info as ast.ArrayFixed).elem_type
}
elem_type_str := g.styp(elem_type)
has_infix_left_var_name := g.write_prepared_tmp_value(past.tmp_var, node, 'int', '0')

mut expr := node.args[0].expr
var_name := g.get_array_expr_param_name(mut expr)

mut closure_var := ''
if mut expr is ast.AnonFn {
if expr.inherited_vars.len > 0 {
closure_var = g.new_tmp_var()
g.declare_closure_fn(mut expr, closure_var)
}
}
i := g.new_tmp_var()
g.writeln('for (int ${i} = 0; ${i} < ${past.tmp_var}_len; ++${i}) {')
g.indent++

g.write_prepared_var(var_name, elem_type, elem_type_str, past.tmp_var, i, left_is_array)
g.set_current_pos_as_last_stmt_pos()
mut is_embed_map_filter := false
match mut expr {
ast.AnonFn {
g.write('if (')
if expr.inherited_vars.len > 0 {
g.write_closure_fn(mut expr, var_name, closure_var)
} else {
g.gen_anon_fn_decl(mut expr)
g.write('${expr.decl.name}(${var_name})')
}
}
ast.Ident {
g.write('if (')
if expr.kind == .function {
g.write('${c_name(expr.name)}(${var_name})')
} else if expr.kind == .variable {
var_info := expr.var_info()
sym_t := g.table.sym(var_info.typ)
if sym_t.kind == .function {
g.write('${c_name(expr.name)}(${var_name})')
} else {
g.expr(expr)
}
} else {
g.expr(expr)
}
}
ast.CallExpr {
if expr.name in ['map', 'filter', 'all', 'any', 'count'] {
is_embed_map_filter = true
g.set_current_pos_as_last_stmt_pos()
}
g.write('if (')
g.expr(expr)
}
ast.LambdaExpr {
g.write('if (')
g.expr(expr.expr)
}
else {
g.write('if (')
g.expr(expr)
}
}
g.writeln2(') {', '\t++${past.tmp_var};')
g.writeln('}')
g.indent--
g.writeln('}')
if !is_embed_map_filter {
g.set_current_pos_as_last_stmt_pos()
}
if has_infix_left_var_name {
g.indent--
g.writeln('}')
g.set_current_pos_as_last_stmt_pos()
}
}

fn (mut g Gen) gen_array_all(node ast.CallExpr) {
past := g.past_tmp_var_new()
defer {
Expand Down Expand Up @@ -1532,7 +1622,7 @@ fn (mut g Gen) gen_array_all(node ast.CallExpr) {
}
}
ast.CallExpr {
if expr.name in ['map', 'filter', 'all', 'any'] {
if expr.name in ['map', 'filter', 'all', 'any', 'count'] {
is_embed_map_filter = true
g.set_current_pos_as_last_stmt_pos()
}
Expand Down
6 changes: 6 additions & 0 deletions vlib/v/gen/c/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,9 @@ fn (mut g Gen) gen_array_method_call(node ast.CallExpr, left_type ast.Type, left
'any' {
g.gen_array_any(node)
}
'count' {
g.gen_array_count(node)
}
'all' {
g.gen_array_all(node)
}
Expand Down Expand Up @@ -1251,6 +1254,9 @@ fn (mut g Gen) gen_fixed_array_method_call(node ast.CallExpr, left_type ast.Type
'any' {
g.gen_array_any(node)
}
'count' {
g.gen_array_count(node)
}
'all' {
g.gen_array_all(node)
}
Expand Down
15 changes: 15 additions & 0 deletions vlib/v/tests/builtin_arrays/array_count_test.v
spytheman marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
fn test_main() {
a := []int{len: 10, init: index}
assert a.count(it % 2) == 5

b := [10]int{init: index}
assert a.count(it % 2) == 5
}

fn test_zero() {
a := []int{len: 10, init: index}
assert a.count(it == 1000) == 0

b := [10]int{init: index}
assert a.count(it == 1000) == 0
felipensp marked this conversation as resolved.
Show resolved Hide resolved
}