diff --git a/compiler/fn.v b/compiler/fn.v index b93fb3de70..da52bbbf72 100644 --- a/compiler/fn.v +++ b/compiler/fn.v @@ -737,7 +737,23 @@ fn (p mut Parser) fn_args(f mut Fn) { if is_mut { p.next() } - mut typ := p.get_type() + mut typ := '' + // variadic arg + if p.tok == .ellipsis { + p.check(.ellipsis) + if p.tok == .rpar { + p.error('you must provide a type for vargs: eg `...string`. multiple types `...` are not supported yet.') + } + t := p.get_type() + vargs_struct := '_V_FnVargs_$f.name' + // register varg struct, incase function is never called + p.fn_define_vargs_stuct(f, t, []string) + p.cgen.typedefs << 'typedef struct $vargs_struct $vargs_struct;\n' + typ = '...$t' + } else { + typ = p.get_type() + } + p.check_and_register_used_imported_type(typ) if is_mut && is_primitive_type(typ) { p.error('mutable arguments are only allowed for arrays, maps, and structs.' + @@ -765,9 +781,14 @@ fn (p mut Parser) fn_args(f mut Fn) { if p.tok == .comma { p.next() } - if p.tok == .dotdot { - f.args << Var{ - name: '..' + // unnamed (C definition) + if p.tok == .ellipsis { + if !f.is_c { + p.error('variadic argument syntax must be `arg_name ...type` eg `argname ...string`.') + } + f.args << Var { + // name: '...' + typ: '...' } p.next() } @@ -779,6 +800,11 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { // println('fn_call_args() name=$f.name args.len=$f.args.len') // C func. # of args is not known p.check(.lpar) + mut is_variadic := false + if f.args.len > 0 { + last_arg := f.args.last() + is_variadic = last_arg.typ.starts_with('...') + } if f.is_c { for p.tok != .rpar { //C.func(var1, var2.method()) @@ -819,7 +845,7 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { continue } // Reached the final vararg? Quit - if i == f.args.len - 1 && arg.name == '..' { + if i == f.args.len - 1 && arg.typ.starts_with('...') { break } ph := p.cgen.add_placeholder() @@ -978,32 +1004,23 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { // Check for commas if i < f.args.len - 1 { // Handle 0 args passed to varargs - is_vararg := i == f.args.len - 2 && f.args[i + 1].name == '..' - if p.tok != .comma && !is_vararg { + if p.tok != .comma && !is_variadic { p.error('wrong number of arguments for $i,$arg.name fn `$f.name`: expected $f.args.len, but got less') } if p.tok == .comma { p.fgen(', ') } - if !is_vararg { + if !is_variadic { p.next() p.gen(',') } } } // varargs - if f.args.len > 0 { - last_arg := f.args.last() - if last_arg.name == '..' { - for p.tok != .rpar { - if p.tok == .comma { - p.gen(',') - p.check(.comma) - } - p.bool_expression() - } - } + if !p.first_pass() && is_variadic { + p.fn_gen_caller_vargs(mut f) } + if p.tok == .comma { p.error('wrong number of arguments for fn `$f.name`: expected $f.args.len, but got more') } @@ -1011,6 +1028,49 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { return f // TODO is return f right? } +fn (p mut Parser) fn_define_vargs_stuct(f &Fn, typ string, values []string) { + vargs_struct := '_V_FnVargs_$f.name' + varg_type := Type{ + cat: TypeCategory.struct_, + name: vargs_struct, + mod: p.mod + } + if values.len > 0 { + p.table.rewrite_type(varg_type) + p.cgen.gen(',&($vargs_struct){.len=$values.len,.args={'+values.join(',')+'}}') + } else { + p.table.register_type2(varg_type) + } + p.table.add_field(vargs_struct, 'len', 'int', false, '', .public) + p.table.add_field(vargs_struct, 'args[$values.len]', typ, false, '', .public) + for va in p.table.varg_access { + if va.fn_name != f.name { continue } + if va.index >= values.len { + p.error_with_token_index('error accessing variadic arg, index `$va.index` out of range.', va.tok_idx) + } + } +} + +fn (p mut Parser) fn_gen_caller_vargs(f mut Fn) { + last_arg := f.args.last() + varg_def_type := last_arg.typ.right(3) + mut varg_values := []string + for p.tok != .rpar { + if p.tok == .comma { + p.check(.comma) + } + p.cgen.start_tmp() + varg_type := p.bool_expression() + varg_value := p.cgen.end_tmp() + p.check_types(last_arg.typ, varg_type) + ref_deref := if last_arg.typ.ends_with('*') && !varg_type.ends_with('*') { '&' } + else if !last_arg.typ.ends_with('*') && varg_type.ends_with('*') { '*' } + else { '' } + varg_values << '$ref_deref$varg_value' + } + p.fn_define_vargs_stuct(f, varg_def_type, varg_values) +} + // "fn (int, string) int" fn (f &Fn) typ_str() string { mut sb := strings.new_builder(50) @@ -1056,8 +1116,8 @@ fn (f &Fn) str_args(table &Table) string { s += ')' } } - else if arg.name == '..' { - s += '...' + else if arg.typ.starts_with('...') { + s += '_V_FnVargs_$f.name *$arg.name' } else { // s += '$arg.typ $arg.name' diff --git a/compiler/parser.v b/compiler/parser.v index 12422a62f4..7b54285805 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -1947,12 +1947,17 @@ fn (p &Parser) fileis(s string) bool { // user.name => `str_typ` is `User` // user.company.name => `str_typ` is `Company` -fn (p mut Parser) dot(str_typ string, method_ph int) string { +fn (p mut Parser) dot(str_typ_ string, method_ph int) string { //if p.fileis('orm_test') { //println('ORM dot $str_typ') //} + mut str_typ := str_typ_ p.check(.dot) - mut typ := p.find_type(str_typ) + is_variadic_arg := str_typ.starts_with('...') + if is_variadic_arg { + str_typ = str_typ.right(3) + } + typ := p.find_type(str_typ) if typ.name.len == 0 { p.error('dot(): cannot find type `$str_typ`') } @@ -1968,6 +1973,14 @@ fn (p mut Parser) dot(str_typ string, method_ph int) string { //} has_field := p.table.type_has_field(typ, p.table.var_cgen_name(field_name)) mut has_method := p.table.type_has_method(typ, field_name) + if is_variadic_arg { + if field_name != 'len' { + p.error('the only field you can access on variadic args is `.len`') + } + p.gen('->$field_name') + p.next() + return 'int' + } // generate `.str()` if !has_method && field_name == 'str' && typ.name.starts_with('array_') { p.gen_array_str(typ) @@ -2086,6 +2099,7 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string { //println('index expr typ=$typ') //println(v.name) //} + is_variadic_arg := typ.starts_with('...') is_map := typ.starts_with('map_') is_str := typ == 'string' is_arr0 := typ.starts_with('array_') @@ -2095,7 +2109,7 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string { mut close_bracket := false if is_indexer { is_fixed_arr := typ[0] == `[` - if !is_str && !is_arr && !is_map && !is_ptr && !is_fixed_arr { + if !is_str && !is_arr && !is_map && !is_ptr && !is_fixed_arr && !is_variadic_arg { p.error('Cant [] non-array/string/map. Got type "$typ"') } p.check(.lsbr) @@ -2125,7 +2139,7 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string { p.gen('[') close_bracket = true } - else if is_ptr { + else if is_ptr && !is_variadic_arg { // typ = 'byte' typ = typ.replace('*', '') // modify(mut []string) fix @@ -2177,6 +2191,27 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string { } p.expr_var = v } + // accessing variadiac args + if !p.first_pass() && is_variadic_arg { + typ = typ.right(3) + if p.calling_c { + p.error('you cannot currently pass varg to a C function.') + } + if !is_indexer { + p.error('You must use array access syntax for variadic arguments.') + } + varg_type := typ.right(3) + l := p.cgen.cur_line.trim_space() + index_val := l.right(l.last_index(' ')).trim_space() + p.cgen.resetln(l.left(fn_ph)) + p.table.varg_access << VargAccess{ + fn_name: p.cur_fn.name, + tok_idx: p.token_idx, + index: index_val.int() + } + p.cgen.set_placeholder(fn_ph, '${v.name}->args[$index_val]') + return typ + } // TODO move this from index_expr() // TODO if p.tok in ... if (p.tok == .assign && !p.is_sql) || p.tok.is_assign() { diff --git a/compiler/scanner.v b/compiler/scanner.v index bbc67f729b..39fc6b49c0 100644 --- a/compiler/scanner.v +++ b/compiler/scanner.v @@ -428,6 +428,10 @@ fn (s mut Scanner) scan() ScanRes { case `.`: if nextc == `.` { s.pos++ + if s.text[s.pos+1] == `.` { + s.pos++ + return scan_res(.ellipsis, '') + } return scan_res(.dotdot, '') } return scan_res(.dot, '') diff --git a/compiler/table.v b/compiler/table.v index bbdae473db..36131774b1 100644 --- a/compiler/table.v +++ b/compiler/table.v @@ -20,9 +20,15 @@ mut: cflags []CFlag // ['-framework Cocoa', '-lglfw3'] fn_cnt int //atomic obfuscate bool + varg_access []VargAccess //names []Name } +struct VargAccess { + fn_name string + tok_idx int + index int +} /* enum NameCategory { @@ -331,6 +337,10 @@ fn (t mut Table) register_fn(new_fn Fn) { fn (table &Table) known_type(typ_ string) bool { mut typ := typ_ + // vararg + if typ.starts_with('...') && typ.len > 3 { + typ = typ.right(3) + } // 'byte*' => look up 'byte', but don't mess up fns if typ.ends_with('*') && !typ.contains(' ') { typ = typ.left(typ.len - 1) @@ -558,6 +568,13 @@ fn (p mut Parser) _check_types(got_, expected_ string, throw bool) bool { if p.pref.translated { return true } + // variadic + if expected.starts_with('...') { + expected = expected.right(3) + } + if got.starts_with('...') { + got = got.right(3) + } // Allow ints to be used as floats if got == 'int' && expected == 'f32' { return true diff --git a/compiler/tests/fn_test.v b/compiler/tests/fn_test.v index 6e7b17f593..8bcec835af 100644 --- a/compiler/tests/fn_test.v +++ b/compiler/tests/fn_test.v @@ -56,13 +56,6 @@ type actionf_p1 fn (voidptr) type actionf_p2 fn (voidptr, voidptr) -fn myprint(s string, ..) { - println('my print') - println('// comment') - println('/* comment */') - println('/* /* comment */ */') -} - // TODO fn modify_array(a mut []int) { a[0] = 10 diff --git a/compiler/tests/fn_variadic_test.v b/compiler/tests/fn_variadic_test.v new file mode 100644 index 0000000000..d0a1c189ef --- /dev/null +++ b/compiler/tests/fn_variadic_test.v @@ -0,0 +1,15 @@ +struct VaTestGroup { + name string +} + +fn variadic_test_a(name string, groups ...VaTestGroup) { + assert groups.len == 2 + assert groups[0].name == 'users' + assert groups[1].name == 'admins' +} + +fn test_fn_variadic() { + group1 := VaTestGroup{name: 'users'} + group2 := VaTestGroup{name: 'admins'} + variadic_test_a('joe', group1, group2) +} diff --git a/compiler/token.v b/compiler/token.v index 4ff1cd0551..2ce83c18ff 100644 --- a/compiler/token.v +++ b/compiler/token.v @@ -68,6 +68,7 @@ enum Token { nl dot dotdot + ellipsis // keywords keyword_beg key_as @@ -150,6 +151,7 @@ fn build_token_str() []string { s[Token.not] = '!' s[Token.dot] = '.' s[Token.dotdot] = '..' + s[Token.ellipsis] = '...' s[Token.comma] = ',' //s[Token.at] = '@' s[Token.semicolon] = ';'