compiler/lang: add variadic function args support
							parent
							
								
									5fb3c0e3a8
								
							
						
					
					
						commit
						f7c8e923c0
					
				
							
								
								
									
										102
									
								
								compiler/fn.v
								
								
								
								
							
							
						
						
									
										102
									
								
								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'
 | ||||
|  |  | |||
|  | @ -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() { | ||||
|  |  | |||
|  | @ -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, '') | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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) | ||||
| } | ||||
|  | @ -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] = ';' | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue