// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module main import strings const ( MaxLocalVars = 50 ) struct Fn { // addr int mut: name string pkg string local_vars []Var var_idx int args []Var is_interface bool // called_fns []string // idx int scope_level int typ string // return type is_c bool receiver_typ string is_public bool is_method bool returns_error bool is_decl bool // type myfn fn(int, int) defer_text string //gen_types []string } fn (f &Fn) find_var(name string) Var { for i in 0 .. f.var_idx { if f.local_vars[i].name == name { return f.local_vars[i] } } return Var{} } fn (f mut Fn) open_scope() { f.scope_level++ } fn (f &Fn) mark_var_used(v Var) { for i, vv in f.local_vars { if vv.name == v.name { //mut ptr := &f.local_vars[i] //ptr.is_used = true f.local_vars[i].is_used = true return } } } fn (f &Fn) mark_var_changed(v Var) { for i, vv in f.local_vars { if vv.name == v.name { //mut ptr := &f.local_vars[i] //ptr.is_used = true f.local_vars[i].is_changed = true // return } } } fn (f &Fn) known_var(name string) bool { v := f.find_var(name) return v.name.len > 0 } fn (f mut Fn) register_var(v Var) { new_var := {v | scope_level: f.scope_level} // Expand the array if f.var_idx >= f.local_vars.len { f.local_vars << new_var } else { f.local_vars[f.var_idx] = new_var } f.var_idx++ } fn (f mut Fn) clear_vars() { f.var_idx = 0 f.local_vars = []Var } // vlib header file? fn (p mut Parser) is_sig() bool { return (p.pref.build_mode == .default_mode || p.pref.build_mode == .build) && (p.file_path.contains(ModPath)) } fn new_fn(pkg string, is_public bool) *Fn { return &Fn { pkg: pkg local_vars: [Var{} ; MaxLocalVars] is_public: is_public } } // Function signatures are added to the top of the .c file in the first run. fn (p mut Parser) fn_decl() { p.fgen('fn ') defer { p.fgenln('\n') } is_pub := p.tok == .key_pub is_live := p.attr == 'live' && !p.pref.is_so && p.pref.is_live if p.attr == 'live' && p.first_pass() && !p.pref.is_live && !p.pref.is_so { println('INFO: run `v -live program.v` if you want to use [live] functions') } if is_pub { p.next() } p.returns = false p.next() mut f := new_fn(p.mod, is_pub) // Method receiver mut receiver_typ := '' if p.tok == .lpar { f.is_method = true p.check(.lpar) receiver_name := p.check_name() is_mut := p.tok == .key_mut is_amp := p.tok == .amp if is_mut || is_amp { p.check_space(p.tok) } receiver_typ = p.get_type() T := p.table.find_type(receiver_typ) if T.is_interface { p.error('invalid receiver type `$receiver_typ` (`$receiver_typ` is an interface)') } // Don't allow modifying types from a different module if !p.first_pass() && !p.builtin_pkg && T.mod != p.mod { println('T.mod=$T.mod') println('p.mod=$p.mod') p.error('cannot define new methods on non-local type `$receiver_typ`') } // (a *Foo) instead of (a mut Foo) is a common mistake if !p.builtin_pkg && receiver_typ.contains('*') { t := receiver_typ.replace('*', '') p.error('use `($receiver_name mut $t)` instead of `($receiver_name *$t)`') } f.receiver_typ = receiver_typ if is_mut || is_amp { receiver_typ += '*' } p.check(.rpar) p.fspace() receiver := Var { name: receiver_name is_arg: true typ: receiver_typ is_mut: is_mut ref: is_amp ptr: is_mut line_nr: p.scanner.line_nr } f.args << receiver f.register_var(receiver) } if p.tok == .plus || p.tok == .minus || p.tok == .mul { f.name = p.tok.str() p.next() } else { f.name = p.check_name() } // C function header def? (fn C.NSMakeRect(int,int,int,int)) is_c := f.name == 'C' && p.tok == .dot // Just fn signature? only builtin.v + default build mode // is_sig := p.builtin_pkg && p.pref.build_mode == default_mode // is_sig := p.pref.build_mode == default_mode && (p.builtin_pkg || p.file.contains(LANG_TMP)) is_sig := p.is_sig() // println('\n\nfn decl !!is_sig=$is_sig name=$f.name $p.builtin_pkg') if is_c { p.check(.dot) f.name = p.check_name() f.is_c = true } else if !p.pref.translated && !p.file_path.contains('view.v') { if contains_capital(f.name) { p.error('function names cannot contain uppercase letters, use snake_case instead') } if f.name.contains('__') { p.error('function names cannot contain double underscores, use single underscores instead') } } // simple_name := f.name // println('!SIMP.le=$simple_name') // user.register() => User_register() has_receiver := receiver_typ.len > 0 if receiver_typ != '' { // f.name = '${receiver_typ}_${f.name}' } // full pkg function name // os.exit ==> os__exit() if !is_c && !p.builtin_pkg && p.mod != 'main' && receiver_typ.len == 0 { f.name = p.prepend_pkg(f.name) } if p.first_pass() && p.table.known_fn(f.name) && receiver_typ.len == 0 { existing_fn := p.table.find_fn(f.name) // This existing function could be defined as C decl before (no body), then we don't need to throw an erro if !existing_fn.is_decl { p.error('redefinition of `$f.name`') } } // Generic? mut is_generic := false if p.tok == .lt { is_generic = true p.next() gen_type := p.check_name() if gen_type != 'T' { p.error('only `T` is allowed as a generic type for now') } p.check(.gt) if p.first_pass() { p.table.register_generic_fn(f.name) } else { //gen_types := p.table.fn_gen_types(f.name) //println(gen_types) } } // Args (...) p.fn_args(mut f) // Returns an error? if p.tok == .not { p.next() f.returns_error = true } // Returns a type? mut typ := 'void' if p.tok == .name || p.tok == .mul || p.tok == .amp || p.tok == .lsbr || p.tok == .question { p.fgen(' ') // TODO In // if p.tok in [ .name, .mul, .amp, .lsbr ] { typ = p.get_type() } // Translated C code can have empty functions (just definitions) is_fn_header := !is_c && !is_sig && (p.pref.translated || p.pref.is_test) && p.tok != .lcbr if is_fn_header { f.is_decl = true } // { required only in normal function declarations if !is_c && !is_sig && !is_fn_header { p.fgen(' ') p.check(.lcbr) } // Register ?option type if typ.starts_with('Option_') { p.cgen.typedefs << 'typedef Option $typ;' } // Register function f.typ = typ mut str_args := f.str_args(p.table) // Special case for main() args if f.name == 'main' && !has_receiver { if str_args != '' || typ != 'void' { p.error('fn main must have no arguments and no return values') } typ = 'int' str_args = 'int argc, char** argv' } dll_export_linkage := if p.os == .msvc && p.attr == 'live' && p.pref.is_so { '__declspec(dllexport) ' } else { '' } if !p.is_vweb { p.cur_fn = f } // Generate `User_register()` instead of `register()` // Internally it's still stored as "register" in type User mut fn_name_cgen := p.table.cgen_name(f) // Start generation of the function body skip_main_in_test := f.name == 'main' && p.pref.is_test if !is_c && !is_live && !is_sig && !is_fn_header && !skip_main_in_test { if p.pref.obfuscate { p.genln('; // $f.name') } // Generate this function's body for all generic types if is_generic { gen_types := p.table.fn_gen_types(f.name) // Remember current scanner position, go back here for each type // TODO remove this once tokens are cached in `new_parser()` cur_pos := p.scanner.pos cur_tok := p.tok cur_lit := p.lit for gen_type in gen_types { p.genln('$dll_export_linkage$typ ${fn_name_cgen}_$gen_type($str_args) {') p.genln('// T start $p.pass ${p.strtok()}') p.cur_gen_type = gen_type // TODO support more than T p.statements() p.scanner.pos = cur_pos p.tok = cur_tok p.lit = cur_lit } } else { p.genln('$dll_export_linkage$typ $fn_name_cgen($str_args) {') } } if is_fn_header { p.genln('$typ $fn_name_cgen($str_args);') p.fgenln('') } if is_c { p.fgenln('\n') } // Register the method if receiver_typ != '' { mut receiver_t := p.table.find_type(receiver_typ) // No such type yet? It could be defined later. Create a new type. // struct declaration later will modify it instead of creating a new one. if p.first_pass() && receiver_t.name == '' { // println('fn decl !!!!!!! REG PH $receiver_typ') p.table.register_type2(Type { name: receiver_typ.replace('*', '') mod: p.mod is_placeholder: true }) } // f.idx = p.table.fn_cnt receiver_t.add_method(f) } else { // println('register_fn typ=$typ isg=$is_generic') p.table.register_fn(f) } if is_sig || p.first_pass() || is_live || is_fn_header || skip_main_in_test { // First pass? Skip the body for now // Look for generic calls. if !is_sig && !is_fn_header { mut opened_scopes := 0 mut closed_scopes := 0 for { if p.tok == .lcbr { opened_scopes++ } if p.tok == .rcbr { closed_scopes++ } p.next() // find `foo()` in function bodies and register generic types // TODO remove this once tokens are cached if p.tok == .gt && p.prev_tok == .name && p.prev_tok2 == .lt && p.scanner.text[p.scanner.pos-1] != `T` { p.scanner.pos -= 3 for p.scanner.pos > 0 && (is_name_char(p.scanner.text[p.scanner.pos]) || p.scanner.text[p.scanner.pos] == `.` || p.scanner.text[p.scanner.pos] == `<` ) { p.scanner.pos-- } p.scanner.pos-- p.next() // Run the function in the firt pass to register the generic type p.name_expr() } if p.tok.is_decl() && !(p.prev_tok == .dot && p.tok == .key_type) { break } // fn body ended, and a new fn attribute declaration like [live] is starting? if closed_scopes > opened_scopes && p.prev_tok == .rcbr { if p.tok == .lsbr { break } } } } // Live code reloading? Load all fns from .so if is_live && p.first_pass() && p.mod == 'main' { //println('ADDING SO FN $fn_name_cgen') p.cgen.so_fns << fn_name_cgen fn_name_cgen = '(* $fn_name_cgen )' } // Function definition that goes to the top of the C file. mut fn_decl := '$dll_export_linkage$typ $fn_name_cgen($str_args)' if p.pref.obfuscate { fn_decl += '; // $f.name' } // Add function definition to the top if !is_c && f.name != 'main' && p.first_pass() { // TODO hack to make Volt compile without -embed_vlib if f.name == 'darwin__nsstring' && p.pref.build_mode == .default_mode { return } p.cgen.fns << fn_decl + ';' } return } if p.attr == 'live' && p.pref.is_so { //p.genln('// live_function body start') p.genln('pthread_mutex_lock(&live_fn_mutex);') } if f.name == 'main' || f.name == 'WinMain' { p.genln('init_consts();') if p.table.imports.contains('os') { if f.name == 'main' { p.genln('os__args = os__init_os_args(argc, argv);') } else if f.name == 'WinMain' { p.genln('os__args = os__parse_windows_cmd_line(pCmdLine);') } } // We are in live code reload mode, call the .so loader in bg if p.pref.is_live { file_base := p.file_path.replace('.v', '') if p.os != .windows && p.os != .msvc { so_name := file_base + '.so' p.genln(' load_so("$so_name"); pthread_t _thread_so; pthread_create(&_thread_so , NULL, &reload_so, NULL); ') } else { so_name := file_base + if p.os == .msvc {'.dll'} else {'.so'} p.genln(' live_fn_mutex = CreateMutexA(0, 0, 0); load_so("$so_name"); unsigned long _thread_so; _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0); ') } } if p.pref.is_test && !p.scanner.file_path.contains('/volt') { p.error('tests cannot have function `main`') } } // println('is_c=$is_c name=$f.name') if is_c || is_sig || is_fn_header { // println('IS SIG .key_returnING tok=${p.strtok()}') return } // Profiling mode? Start counting at the beginning of the function (save current time). if p.pref.is_prof && f.name != 'main' && f.name != 'time__ticks' { p.genln('double _PROF_START = time__ticks();//$f.name') cgen_name := p.table.cgen_name(f) f.defer_text = ' ${cgen_name}_time += time__ticks() - _PROF_START;' } if is_generic { // Don't need to generate body for the actual generic definition p.cgen.nogen = true } p.statements_no_rcbr() p.cgen.nogen = false // Print counting result after all statements in main if p.pref.is_prof && f.name == 'main' { p.genln(p.print_prof_counters()) } // Counting or not, always need to add defer before the end p.genln(f.defer_text) if typ != 'void' && !p.returns && f.name != 'main' && f.name != 'WinMain' { p.error('$f.name must return "$typ"') } if p.attr == 'live' && p.pref.is_so { //p.genln('// live_function body end') p.genln('pthread_mutex_unlock(&live_fn_mutex);') } // {} closed correctly? scope_level should be 0 if p.mod == 'main' { // println(p.cur_fn.scope_level) } if p.cur_fn.scope_level > 2 { // p.error('unclosed {') } // Make sure all vars in this function are used (only in main for now) // if p.builtin_pkg || p.mod == 'os' ||p.mod=='http'{ if p.mod != 'main' { if !is_generic { p.genln('}') } return } p.check_unused_variables() p.cur_fn = EmptyFn if !is_generic { p.genln('}') } } fn (p mut Parser) check_unused_variables() { for var in p.cur_fn.local_vars { if var.name == '' { break } if !var.is_used && !p.pref.is_repl && !var.is_arg && !p.pref.translated && var.name != '_' { p.scanner.line_nr = var.line_nr - 1 p.error('`$var.name` declared and not used') } if !var.is_changed && var.is_mut && !p.pref.is_repl && !var.is_arg && !p.pref.translated && var.name != '_' { p.scanner.line_nr = var.line_nr - 1 p.error('`$var.name` is declared as mutable, but it was never changed') } } } // user.register() => "User_register(user)" // method_ph - where to insert "user_register(" // receiver_var - "user" (needed for pthreads) // receiver_type - "User" fn (p mut Parser) async_fn_call(f Fn, method_ph int, receiver_var, receiver_type string) { // println('\nfn_call $f.name is_method=$f.is_method receiver_type=$f.receiver_type') // p.print_tok() mut thread_name := '' // Normal function => just its name, method => TYPE_FN.name mut fn_name := f.name if f.is_method { receiver_type = receiver_type.replace('*', '') fn_name = '${receiver_type}_${f.name}' } // Generate tmp struct with args arg_struct_name := 'thread_arg_$fn_name' tmp_struct := p.get_tmp() p.genln('$arg_struct_name * $tmp_struct = malloc(sizeof($arg_struct_name));') mut arg_struct := 'typedef struct $arg_struct_name { ' p.next() p.check(.lpar) // str_args contains the args for the wrapper function: // wrapper(arg_struct * arg) { fn("arg->a, arg->b"); } mut str_args := '' mut did_gen_something := false for i, arg in f.args { arg_struct += '$arg.typ $arg.name ;'// Add another field (arg) to the tmp struct definition str_args += 'arg->$arg.name' if i == 0 && f.is_method { p.genln('$tmp_struct -> $arg.name = $receiver_var ;') if i < f.args.len - 1 { str_args += ',' } continue } // Set the struct values (args) p.genln('$tmp_struct -> $arg.name = ') p.expression() p.genln(';') if i < f.args.len - 1 { p.check(.comma) str_args += ',' } did_gen_something = true } if !did_gen_something { // Msvc doesnt like empty struct arg_struct += 'EMPTY_STRUCT_DECLARATION' } arg_struct += '} $arg_struct_name ;' // Also register the wrapper, so we can use the original function without modifying it fn_name = p.table.cgen_name(f) wrapper_name := '${fn_name}_thread_wrapper' wrapper_text := 'void* $wrapper_name($arg_struct_name * arg) {$fn_name( /*f*/$str_args ); }' p.cgen.register_thread_fn(wrapper_name, wrapper_text, arg_struct) // Create thread object tmp_nr := p.get_tmp_counter() thread_name = '_thread$tmp_nr' if p.os != .windows && p.os != .msvc { p.genln('pthread_t $thread_name;') } tmp2 := p.get_tmp() mut parg := 'NULL' if f.args.len > 0 { parg = ' $tmp_struct' } // Call the wrapper if p.os == .windows || p.os == .msvc { p.genln(' CreateThread(0,0, $wrapper_name, $parg, 0,0);') } else { p.genln('int $tmp2 = pthread_create(& $thread_name, NULL, $wrapper_name, $parg);') } p.check(.rpar) } fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type string) { if !f.is_public && !f.is_c && !p.pref.is_test && !f.is_interface && f.pkg != p.mod { p.error('function `$f.name` is private') } p.calling_c = f.is_c if f.is_c && !p.builtin_pkg { if f.name == 'free' { p.error('use `free()` instead of `C.free()`') } else if f.name == 'malloc' { p.error('use `malloc()` instead of `C.malloc()`') } } mut cgen_name := p.table.cgen_name(f) p.next() mut gen_type := '' if p.tok == .lt { p.check(.lt) gen_type = p.check_name() // `foo()` // If we are in the first pass, we need to add `Bar` type to the generic function `foo`, // so that generic `foo`s body can be generated for each type in the second pass. if p.first_pass() { //println('registering $gen_type in $f.name') p.table.register_generic_fn_type(f.name, gen_type) // Function bodies are skipped in the first passed, we only need to register the generic type here. return } cgen_name += '_' + gen_type p.check(.gt) } // if p.pref.is_prof { // p.cur_fn.called_fns << cgen_name // } // Normal function call if !f.is_method { p.gen(cgen_name) p.gen('(') // p.fgen(f.name) } // If we have a method placeholder, // we need to preappend "method(receiver, ...)" else { mut method_call := '${cgen_name}(' receiver := f.args.first() if receiver.is_mut && !p.expr_var.is_mut { println('$method_call recv=$receiver.name recv_mut=$receiver.is_mut') p.error('`$p.expr_var.name` is immutable') } if !p.expr_var.is_changed { p.cur_fn.mark_var_changed(p.expr_var) } // if receiver is key_mut or a ref (&), generate & for the first arg if receiver.ref || (receiver.is_mut && !receiver_type.contains('*')) { method_call += '& /* ? */' } // generate deref (TODO copy pasta later in fn_call_args) if !receiver.is_mut && receiver_type.contains('*') { method_call += '*' } mut cast := '' // Method returns (void*) => cast it to int, string, user etc // number := *(int*)numbers.first() if f.typ == 'void*' { // array_int => int cast = receiver_type.all_after('_') cast = '*($cast*) ' } p.cgen.set_placeholder(method_ph, '$cast $method_call') } // foo() p.fn_call_args(mut f) p.gen(')') p.calling_c = false // println('end of fn call typ=$f.typ') } // for declaration // return an updated Fn object with args[] field set fn (p mut Parser) fn_args(f mut Fn) { p.check(.lpar) defer { p.check(.rpar) } if f.is_interface { int_arg := Var { typ: f.receiver_typ } f.args << int_arg } // Just register fn arg types types_only := p.tok == .mul || (p.peek() == .comma && p.table.known_type(p.lit)) || p.peek() == .rpar// (int, string) if types_only { for p.tok != .rpar { typ := p.get_type() v := Var { typ: typ is_arg: true // is_mut: is_mut line_nr: p.scanner.line_nr } // f.register_var(v) f.args << v if p.tok == .comma { p.next() } } } // (a int, b,c string) syntax for p.tok != .rpar { mut names := [ p.check_name() ] // a,b,c int syntax for p.tok == .comma { p.check(.comma) p.fspace() names << p.check_name() } p.fspace() is_mut := p.tok == .key_mut if is_mut { p.next() } mut typ := p.get_type() if is_mut && is_primitive_type(typ) { p.error('mutable arguments are only allowed for arrays, maps, and structs.' + '\nreturn values instead: `foo(n mut int)` => `foo(n int) int`') } for name in names { if !p.first_pass() && !p.table.known_type(typ) { p.error('fn_args: unknown type $typ') } if is_mut { typ += '*' } v := Var { name: name typ: typ is_arg: true is_mut: is_mut ptr: is_mut line_nr: p.scanner.line_nr } f.register_var(v) f.args << v } if p.tok == .comma { p.next() } if p.tok == .dotdot { f.args << Var { name: '..' } p.next() } } } // foo *(1, 2, 3, mut bar)* fn (p mut Parser) fn_call_args(f mut Fn) *Fn { // p.gen('(') // println('fn_call_args() name=$f.name args.len=$f.args.len') // C func. # of args is not known // if f.name.starts_with('c_') { p.check(.lpar) if f.is_c { for p.tok != .rpar { p.bool_expression() if p.tok == .comma { p.gen(', ') p.check(.comma) } } p.check(.rpar) return f } // add debug information to panic when -debug arg is passed if p.v.pref.is_debug && f.name == 'panic' { mod_name := p.mod.replace('_dot_', '.') fn_name := p.cur_fn.name.replace('${p.mod}__', '') file_path := p.file_path.replace('\\', '\\\\') // escape \ p.cgen.resetln(p.cgen.cur_line.replace( 'v_panic (', '_panic_debug ($p.scanner.line_nr, tos2("$file_path"), tos2("$mod_name"), tos2("$fn_name"), ' )) } // Receiver - first arg for i, arg in f.args { // println('$i) arg=$arg.name') // Skip receiver, because it was already generated in the expression if i == 0 && f.is_method { if f.args.len > 1 { p.gen(',') } continue } // Reached the final vararg? Quit if i == f.args.len - 1 && arg.name == '..' { break } ph := p.cgen.add_placeholder() // `)` here means that not enough args were provided if p.tok == .rpar { str_args := f.str_args(p.table)// TODO this is C args p.error('not enough arguments in call to `$f.name ($str_args)`') } // If `arg` is mutable, the caller needs to provide `mut`: // `mut numbers := [1,2,3]; reverse(mut numbers);` if arg.is_mut { if p.tok != .key_mut { p.error('`$arg.name` is a key_mut argument, you need to provide `mut`: `$f.name(...mut a...)`') } if p.peek() != .name { p.error('`$arg.name` is a key_mut argument, you need to provide a variable to modify: `$f.name(... mut a...)`') } p.check(.key_mut) var_name := p.lit v := p.cur_fn.find_var(var_name) if v.name == '' { p.error('`$arg.name` is a key_mut argument, you need to provide a variable to modify: `$f.name(... mut a...)`') } if !v.is_changed { p.cur_fn.mark_var_changed(v) } } p.expected_type = arg.typ typ := p.bool_expression() // Optimize `println`: replace it with `printf` to avoid extra allocations and // function calls. `println(777)` => `printf("%d\n", 777)` // (If we don't check for void, then V will compile `println(func())`) if i == 0 && f.name == 'println' && typ != 'string' && typ != 'void' { T := p.table.find_type(typ) $if !windows { fmt := p.typ_to_fmt(typ, 0) if fmt != '' { p.cgen.resetln(p.cgen.cur_line.replace('println (', '/*opt*/printf ("' + fmt + '\\n", ')) continue } } if typ.ends_with('*') { p.cgen.set_placeholder(ph, 'ptr_str(') p.gen(')') continue } // Make sure this type has a `str()` method if !T.has_method('str') { error_msg := ('`$typ` needs to have method `str() string` to be printable') if T.fields.len > 0 { mut index := p.cgen.cur_line.len - 1 for index > 0 && p.cgen.cur_line[index] != ` ` { index-- } name := p.cgen.cur_line.right(index + 1) if name == '}' { p.error(error_msg) } p.cgen.resetln(p.cgen.cur_line.left(index)) p.scanner.create_type_string(T, name) p.cgen.cur_line.replace(typ, '') p.next() return p.fn_call_args(mut f) } p.error(error_msg) } p.cgen.set_placeholder(ph, '${typ}_str(') p.gen(')') continue } got := typ expected := arg.typ // println('fn arg got="$got" exp="$expected"') if !p.check_types_no_throw(got, expected) { mut err := 'Fn "$f.name" wrong arg #${i+1}. ' err += 'Expected "$arg.typ" ($arg.name) but got "$typ"' p.error(err) } is_interface := p.table.is_interface(arg.typ) // Add & or * before arg? if !is_interface { // Dereference if got.contains('*') && !expected.contains('*') { p.cgen.set_placeholder(ph, '*') } // Reference // TODO ptr hacks. DOOM hacks, fix please. if !got.contains('*') && expected.contains('*') && got != 'voidptr' { // println('\ne:"$expected" got:"$got"') if ! (expected == 'void*' && got == 'int') && ! (expected == 'byte*' && got.contains(']byte')) && ! (expected == 'byte*' && got == 'string') { p.cgen.set_placeholder(ph, '& /*11 EXP:"$expected" GOT:"$got" */') } } } // interface? if is_interface { if !got.contains('*') { p.cgen.set_placeholder(ph, '&') } // Pass all interface methods interface_type := p.table.find_type(arg.typ) for method in interface_type.methods { p.gen(', ${typ}_${method.name} ') } } // 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 { 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 { 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.tok == .comma { p.error('wrong number of arguments for fn `$f.name`: expected $f.args.len, but got more') } p.check(.rpar) // p.gen(')') } // "fn (int, string) int" fn (f Fn) typ_str() string { mut sb := strings.new_builder(50) sb.write('fn (') for i, arg in f.args { sb.write(arg.typ) if i < f.args.len - 1 { sb.write(',') } } sb.write(')') if f.typ != 'void' { sb.write(' $f.typ') } return sb.str() } // f.args => "int a, string b" fn (f &Fn) str_args(table *Table) string { mut s := '' for i, arg in f.args { // Interfaces are a special case. We need to pass the object + pointers // to all methods: // fn handle(r Runner) { => // void handle(void *r, void (*Runner_run)(void*)) { if table.is_interface(arg.typ) { // First the object (same name as the interface argument) s += ' void* $arg.name' // Now all methods interface_type := table.find_type(arg.typ) for method in interface_type.methods { s += ', $method.typ (*${arg.typ}_${method.name})(void*' if method.args.len > 1 { for a in method.args.right(1) { s += ', $a.typ' } } s += ')' } } else if arg.name == '..' { s += '...' } else { // s += '$arg.typ $arg.name' s += table.cgen_name_type_pair(arg.name, arg.typ)// '$arg.typ $arg.name' } if i < f.args.len - 1 { s += ', ' } } return s }