// 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 compiler import( strings ) const ( MaxLocalVars = 50 ) pub struct Fn { // addr int pub: mut: name string mod string //local_vars []Var //var_idx int args []Var is_interface bool // called_fns []string // idx int scope_level int typ string // return type receiver_typ string is_c bool is_public bool is_method bool is_decl bool // type myfn fn(int, int) is_unsafe bool is_deprecated bool is_variadic bool is_generic bool returns_error bool defer_text []string type_pars []string type_inst []TypeInst dispatch_of TypeInst // current type inst of this generic instance body_idx int // idx of the first body statement fn_name_token_idx int // used by error reporting comptime_define string is_used bool // so that we can skip unused fns in resulting C code } struct TypeInst { mut: // an instantiation of generic params (e.g. ["int","int","double"]) inst map[string]string done bool } const ( EmptyFn = Fn{} MainFn = Fn{ name: 'main' } ) fn (a []TypeInst) str() string { mut r := []string for t in a { mut s := ' | ' for k in t.inst.keys() { s += k+' -> '+ t.inst[k] +' | ' } r << s } return r.str() } fn (p &Parser) find_var(name string) ?Var { for i in 0 .. p.var_idx { if p.local_vars[i].name == name { return p.local_vars[i] } } return none } fn (p &Parser) find_var_check_new_var(name string) ?Var { for i in 0 .. p.var_idx { if p.local_vars[i].name == name { return p.local_vars[i] } } // A hack to allow `newvar := Foo{ field: newvar }` // Declare the variable so that it can be used in the initialization if name == 'main__' + p.var_decl_name { return Var{ name : p.var_decl_name typ : 'voidptr' is_mut : true } } return none } fn (p mut Parser) open_scope() { p.cur_fn.defer_text << '' p.cur_fn.scope_level++ } fn (p mut Parser) mark_var_used(v Var) { if v.idx == -1 || v.idx >= p.local_vars.len { return } p.local_vars[v.idx].is_used = true } fn (p mut Parser) mark_var_returned(v Var) { if v.idx == -1 || v.idx >= p.local_vars.len { return } p.local_vars[v.idx].is_returned = true } fn (p mut Parser) mark_var_changed(v Var) { if v.idx == -1 || v.idx >= p.local_vars.len { return } p.local_vars[v.idx].is_changed = true } fn (p mut Parser) mark_arg_moved(v Var) { for i, arg in p.cur_fn.args { if arg.name == v.name { //println('setting f $p.cur_fn.name arg $arg.name to is_mut') p.cur_fn.args[i].is_moved = true break } } p.table.fns[p.cur_fn.name] = p.cur_fn } fn (p mut Parser) known_var(name string) bool { _ = p.find_var(name) or { return false } return true } fn (p mut Parser) known_var_check_new_var(name string) bool { _ = p.find_var_check_new_var(name) or { return false } return true } fn (p mut Parser) register_var(v Var) { mut new_var := {v | idx: p.var_idx, scope_level: p.cur_fn.scope_level} if v.line_nr == 0 { new_var.token_idx = p.cur_tok_index() new_var.line_nr = p.cur_tok().line_nr } // Expand the array if p.var_idx >= p.local_vars.len { p.local_vars << new_var } else { p.local_vars[p.var_idx] = new_var } p.var_idx++ } fn (p mut Parser) clear_vars() { // shared a := [1, 2, 3] p.var_idx = 0 if p.local_vars.len > 0 { if p.pref.autofree { //p.local_vars.free() } p.local_vars = []Var } } // Function signatures are added to the top of the .c file in the first run. fn (p mut Parser) fn_decl() { p.clear_vars() // clear local vars every time a new fn is started p.fgen('fn ') //defer { p.fgenln('\n') } // If we are in the first pass, create a new function. // In the second pass fetch the one we created. /* mut f := if p.first_pass { Fn{ mod: p.mod is_public: p.tok == .key_pub } else { } */ is_pub := p.tok == .key_pub mut f := Fn{ mod: p.mod is_public: is_pub || p.is_vh // functions defined in .vh are always public is_unsafe: p.attr == 'unsafe_fn' is_deprecated: p.attr == 'deprecated' comptime_define: if p.attr.starts_with('if ') { p.attr.right(3) } else { '' } } 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.gen('/* returns $p.returns */') p.next() // 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.name == '' || t.is_placeholder) && !p.first_pass() { p.error('unknown receiver type `$receiver_typ`') } if t.cat == .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_mod && t.mod != p.mod && !p.is_vgen // allow .str() { //println('T.mod=$T.mod') //println('p.mod=$p.mod') p.error('cannot define new methods on non-local type `$receiver_typ`') } // `(f *Foo)` instead of `(f mut Foo)` is a common mistake if receiver_typ.ends_with('*') { tt := receiver_typ.replace('*', '') p.error('use `($receiver_name mut $tt)` instead of `($receiver_name *$tt)`') } 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 token_idx: p.cur_tok_index() } f.args << receiver p.register_var(receiver) } // +-/* methods if p.tok in [.plus, .minus, .mul, .div, .mod] { f.name = p.tok.str() p.next() } else { f.name = p.check_name() } f.fn_name_token_idx = p.cur_tok_index() // init fn if f.name == 'init' && !f.is_method && f.is_public && !p.is_vh { p.error('init function cannot be public') } // 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 if p.is_vh { //println('\n\nfn_decl() name=$f.name receiver_typ=$receiver_typ nogen=$p.cgen.nogen') } if is_c { p.check(.dot) f.name = p.check_name() f.is_c = true } else if !p.pref.translated { if contains_capital(f.name) && !p.fileis('view.v') { println('`$f.name`') p.error('function names cannot contain uppercase letters, use snake_case instead') } if f.name[0] == `_` { p.error('function names cannot start with `_`, use snake_case instead') } if f.name.contains('__') { p.error('function names cannot contain double underscores, use single underscores instead') } } // simple_name := f.name // user.register() => User_register() has_receiver := receiver_typ.len > 0 if receiver_typ != '' { // f.name = '${receiver_typ}_${f.name}' } // full mod function name // `os.exit()` ==> `os__exit()` // if !is_c && !p.builtin_mod && receiver_typ.len == 0 { if !is_c && !has_receiver && (!p.builtin_mod || (p.builtin_mod && f.name == 'init')) { f.name = p.prepend_mod(f.name) } if p.first_pass() && receiver_typ.len == 0 { if 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 error. if !existing_fn.is_decl { p.error('redefinition of `$f.name`') } } } // Generic? if p.tok == .lt { f.is_generic = true p.next() for { type_par := p.check_name() if type_par.len > 1 || !(type_par in reserved_type_param_names) { p.error('type parameters must be single-character, upper-case letters of the following set: $reserved_type_param_names') } if type_par in f.type_pars { p.error('redeclaration of type parameter `$type_par`') } f.type_pars << type_par if p.tok == .gt { break } p.check(.comma) } p.set_current_fn(f) p.check(.gt) } // 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 in [.name, .mul, .amp, .lsbr, .question, .lpar] { p.fgen(' ') typ = p.get_type() } // V allows empty functions (just definitions) is_fn_header := !is_c && !p.is_vh && p.tok != .lcbr if is_fn_header { f.is_decl = true } // `{` required only in normal function declarations if !is_c && !p.is_vh && !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 str_args := f.str_args(p.table) // Special case for main() args if f.name == 'main__main' && !has_receiver { if str_args != '' || typ != 'void' { p.error_with_token_index('fn main must have no arguments and no return values', f.fn_name_token_idx) } } dll_export_linkage := p.get_linkage_prefix() if !p.is_vweb { p.set_current_fn( f ) } // Generate `User_register()` instead of `register()` // Internally it's still stored as "register" in type User mut fn_name_cgen := p.table.fn_gen_name(f) // Start generation of the function body skip_main_in_test := false if !is_c && !is_live && !p.is_vh && !is_fn_header && !skip_main_in_test { if p.pref.obfuscate { p.genln('; // $f.name') } // Generic functions are inserted as needed from the call site if f.is_generic { if p.first_pass() { f.body_idx = p.cur_tok_index()+1 if f.is_method { rcv := p.table.find_type(receiver_typ) if p.first_pass() && rcv.name == '' { r := Type { name: rcv.name.replace('*', '') mod: p.mod is_placeholder: true } p.table.register_type2(r) } // println('added generic method $rcv.name $f.name') p.add_method(rcv.name, f) } else { p.table.register_fn(f) } } if f.is_method { p.mark_var_changed(f.args[0]) } p.check_unused_variables() p.set_current_fn( EmptyFn ) p.returns = false p.skip_fn_body() return } else { p.gen_fn_decl(f, typ, 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 ! registering placeholder $receiver_typ') receiver_t = Type { name: receiver_typ.replace('*', '') mod: p.mod is_placeholder: true } p.table.register_type2(receiver_t) } p.add_method(receiver_t.name, f) } else if p.first_pass(){ // println('register_fn $f.name typ=$typ isg=$is_generic pass=$p.pass ' + //'$p.file_name') p.table.register_fn(f) } if p.is_vh || p.first_pass() || is_live || is_fn_header || skip_main_in_test { // First pass? Skip the body for now // Look for generic calls. if !p.is_vh && !is_fn_header { p.skip_fn_body() } // 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 && p.first_pass() { 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 in ['main__main', 'main', 'WinMain'] { if p.pref.is_test { p.error_with_token_index('tests cannot have function `main`', f.fn_name_token_idx) } } // println('is_c=$is_c name=$f.name') if is_c || p.is_vh || is_fn_header { return } // Profiling mode? Start counting at the beginning of the function (save current time). if p.pref.is_prof && f.name != 'time__ticks' { p.genln('double _PROF_START = time__ticks();//$f.name') cgen_name := p.table.fn_gen_name(f) if f.defer_text.len > f.scope_level { f.defer_text[f.scope_level] = ' ${cgen_name}_time += time__ticks() - _PROF_START;' } } 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 if !p.is_vweb { if f.defer_text.len > f.scope_level { p.genln(f.defer_text[f.scope_level]) } } if typ != 'void' && !p.returns { p.error_with_token_index('$f.name must return "$typ"', f.fn_name_token_idx) } 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.mod != 'main' { p.genln('}') return } p.genln('}') p.check_unused_variables() p.set_current_fn( EmptyFn ) p.returns = false } [inline] // Skips the entire function's body in the first pass. fn (p mut Parser) skip_fn_body() { mut opened_scopes := 0 mut closed_scopes := 0 for { if p.tok == .lcbr { opened_scopes++ } if p.tok == .rcbr { closed_scopes++ } // find `foo()` in function bodies and register generic types // TODO // ... // Reached a declaration token? (fn, struct, const etc) Stop. if p.tok.is_decl() { 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 } } p.next() } } fn (p Parser) get_linkage_prefix() string { return if p.pref.ccompiler == 'msvc' && p.attr == 'live' && p.pref.is_so { '__declspec(dllexport) ' } else if p.attr == 'inline' { 'static inline ' } else { '' } } fn (p mut Parser) check_unused_variables() { for var in p.local_vars { if var.name == '' { break } if !var.is_used && !p.pref.is_repl && !var.is_arg && !p.pref.translated { p.production_error_with_token_index('`$var.name` declared and not used', var.token_idx ) } if !var.is_changed && var.is_mut && !p.pref.is_repl && !p.pref.translated && var.typ != 'T*' { p.error_with_token_index('`$var.name` is declared as mutable, but it was never changed', var.token_idx ) } } } // 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 { fn_name = receiver_type.replace('*', '') + '_' + f.name //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 $dot_ptr $arg.name' if i == 0 && f.is_method { p.genln('$tmp_struct $dot_ptr $arg.name = $receiver_var ;') if i < f.args.len - 1 { str_args += ',' } did_gen_something = true continue } // Set the struct values (args) p.genln('$tmp_struct $dot_ptr $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.fn_gen_name(f) wrapper_name := '${fn_name}_thread_wrapper' mut wrapper_type := 'void*' if p.os == .windows { wrapper_type = 'void* __stdcall' } wrapper_text := '$wrapper_type $wrapper_name($arg_struct_name * arg) {$fn_name( /*f*/$str_args ); return NULL; }' 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.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.genln(' CreateThread(0,0, $wrapper_name, $parg, 0,0);') } else { p.genln('int $tmp2 = pthread_create(& $thread_name, NULL, (void *)$wrapper_name, $parg);') } p.check(.rpar) } // p.tok == fn_name fn (p mut Parser) fn_call(f mut Fn, method_ph int, receiver_var, receiver_type string) { if f.is_unsafe && !p.builtin_mod && !p.inside_unsafe { p.warn('you are calling an unsafe function outside of an unsafe block') } if f.is_deprecated { p.warn('$f.name is deprecated') } if !f.is_public && !f.is_c && !p.pref.is_test && !f.is_interface && f.mod != p.mod { if f.name == 'contains' { println('use `value in numbers` instead of `numbers.contains(value)`') } p.error('function `$f.name` is private') } is_comptime_define := f.comptime_define != '' && f.comptime_define != p.pref.comptime_define if is_comptime_define { p.cgen.nogen = true } p.calling_c = f.is_c if f.is_c && !p.builtin_mod { 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()`') } } f.is_used = true cgen_name := p.table.fn_gen_name(f) p.next() // fn name if p.tok == .lt { mut i := p.token_idx for { if p.tokens[i].tok == .gt { p.error('explicit type arguments are not allowed; remove `<...>`') } else if p.tokens[i].tok == .lpar { // probably a typo, do not concern the user with the above error message break } i += 1 } } // if p.pref.is_prof { // p.cur_fn.called_fns << cgen_name // } // If we have a method placeholder, // we need to preappend "method(receiver, ...)" if f.is_method { receiver := f.args.first() //println('r=$receiver.typ RT=$receiver_type') if receiver.is_mut && !p.expr_var.is_mut { //println('$method_call recv=$receiver.name recv_mut=$receiver.is_mut') if p.expr_var.is_for_var { p.error('`$p.expr_var.name` is immutable, `for` variables' + ' always are') } else { p.error('`$p.expr_var.name` is immutable, declare it with `mut`') } } if !p.expr_var.is_changed && receiver.is_mut { p.mark_var_changed(p.expr_var) } p.gen_method_call(receiver, receiver_type, cgen_name, f.typ, method_ph) } else { // Normal function call p.gen('$cgen_name (') } // `foo()` // if f is generic, the name is changed to a suitable instance in dispatch_generic_fn_instance() // we then replace `cgen_name` with the instance's name generic := f.is_generic p.fn_call_args(mut f) if generic { p.cgen.resetln(p.cgen.cur_line.replace('$cgen_name (', '$f.name (')) // println('calling inst $f.name: $p.cgen.cur_line') } p.gen(')') p.calling_c = false if is_comptime_define { p.cgen.nogen = false p.cgen.resetln('') } // 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 token_idx: p.cur_tok_index() } f.args << int_arg } // `(int, string, int)` // Just register fn arg types types_only := p.tok == .mul || p.tok == .amp || (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() p.check_and_register_used_imported_type(typ) v := Var { typ: typ is_arg: true // is_mut: is_mut line_nr: p.scanner.line_nr token_idx: p.cur_tok_index() } // 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.check(.key_mut) } // 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.') } f.is_variadic = true } mut typ := p.get_type() if !p.first_pass() && !p.table.known_type(typ) { p.error('fn_args: unknown type $typ') } if f.is_variadic { if !f.is_c { // register varg struct, incase function is never called if p.first_pass() && !f.is_generic { p.register_vargs_stuct(typ, 0) } typ = 'varg_$typ' } else { typ = '...$typ' // TODO: fix, this is invalid in C } } 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.' + '\nreturn values instead: `foo(n mut int)` => `foo(n int) int`') } for name in names { 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 token_idx: p.cur_tok_index() } p.register_var(v) f.args << v } if p.tok == .comma { p.check(.comma) } // 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() } } } // foo *(1, 2, 3, mut bar)* fn (p mut Parser) fn_call_args(f mut Fn) { // println('fn_call_args() name=$f.name args.len=$f.args.len') // C func. # of args is not known p.check(.lpar) if f.is_c { for p.tok != .rpar { //C.func(var1, var2.method()) //If the parameter calls a function or method that is not C, //the value of p.calling_c is changed p.calling_c = true ph := p.cgen.add_placeholder() typ := p.bool_expression() // Cast V byteptr to C char* (byte is unsigned in V, that led to C warnings) if typ == 'byte*' { p.cgen.set_placeholder(ph, '(char*)') } if p.tok == .comma { p.gen(', ') p.check(.comma) } } p.check(.rpar) return } // add debug information to panic when -g arg is passed if p.v.pref.is_debug && f.name == 'panic' && !p.is_js { mod_name := p.mod.replace('_dot_', '.') fn_name := p.cur_fn.name.replace('${p.mod}__', '') file_path := cescaped_path(p.file_path) p.cgen.resetln(p.cgen.cur_line.replace( 'v_panic (', 'panic_debug ($p.scanner.line_nr, tos3("$file_path"), tos3("$mod_name"), tos2((byte *)"$fn_name"), ' )) } mut saved_args := []string for i, arg in f.args { // Receiver is the first arg // Skip the receiver, because it was already generated in the expression if i == 0 && f.is_method { if f.args.len > 1 { // && !p.is_js { p.gen(',') } continue } // Reached the final vararg? Quit if i == f.args.len - 1 && arg.typ.starts_with('varg_') { 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.tok == .name { mut dots_example := 'mut $p.lit' if i > 0 { dots_example = '.., ' + dots_example } if i < f.args.len - 1 { dots_example = dots_example + ',..' } p.error('`$arg.name` is a mutable argument, you need to provide `mut`: `$f.name($dots_example)`') } if p.peek() != .name { p.error('`$arg.name` is a mutable argument, you need to provide a variable to modify: `$f.name(... mut a...)`') } p.check(.key_mut) var_name := p.lit v := p.find_var(var_name) or { p.error('`$arg.name` is a mutable argument, you need to provide a variable to modify: `$f.name(... mut a...)`') exit(1) } if !v.is_changed { p.mark_var_changed(v) } } p.expected_type = arg.typ clone := p.pref.autofree && arg.typ == 'string' && arg.is_moved && p.mod != 'builtin' if clone { p.gen('/*YY f=$f.name arg=$arg.name is_moved=$arg.is_moved*/string_clone(') } mut typ := p.bool_expression() if clone { p.gen(')') } // 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' || f.name == 'print') && typ == 'ustring' { if typ == 'ustring' { p.gen('.s') } typ = 'string' } if i == 0 && (f.name == 'println' || f.name == 'print') && !(typ in ['string', 'ustring', 'void' ]) { T := p.table.find_type(typ) $if !windows { $if !js { fmt := p.typ_to_fmt(typ, 0) if fmt != '' && typ != 'bool' { nl := if f.name == 'println' { '\\n' } else { '' } p.cgen.resetln(p.cgen.cur_line.replace(f.name + ' (', '/*opt*/printf ("' + fmt + '$nl", ')) continue } } } if typ.ends_with('*') { p.cgen.set_placeholder(ph, 'ptr_str(') p.gen(')') continue } // Make sure this type has a `str()` method $if !js { if !T.has_method('str') { // varg if T.name.starts_with('varg_') { p.gen_varg_str(T) p.cgen.set_placeholder(ph, '${typ}_str(') p.gen(')') continue } // Arrays have automatic `str()` methods else if T.name.starts_with('array_') { p.gen_array_str(T) p.cgen.set_placeholder(ph, '${typ}_str(') p.gen(')') continue } // struct else if T.cat == .struct_ { p.gen_struct_str(T) p.cgen.set_placeholder(ph, '${typ}_str(') p.gen(')') continue } error_msg := ('`$typ` needs to have method `str() string` to be printable') p.error(error_msg) } p.cgen.set_placeholder(ph, '${typ}_str(') p.gen(')') } continue } got := typ expected := arg.typ got_ptr := got.ends_with('*') exp_ptr := expected.ends_with('*') // println('fn arg got="$got" exp="$expected"') type_mismatch := !p.check_types_no_throw(got, expected) if type_mismatch && f.is_generic { // println("argument `$arg.name` is generic") saved_args << got } else if type_mismatch { mut j := i if f.is_method { j-- } mut nr := '${i+1}th' if j == 0 { nr = 'first' } else if j == 1 { nr = 'second' } else if j == 2 { nr = 'third' } p.error('cannot use type `$typ` as type `$arg.typ` in $nr ' + 'argument to `$f.name()`') } else { saved_args << '' } is_interface := p.table.is_interface(arg.typ) // Automatically add `&` or `*` before an argument. // V, unlike C and Go, simplifies this aspect: // `foo(bar)` is allowed where `foo(&bar)` is expected. // The argument is not mutable, so it won't be changed by the function. // It doesn't matter whether it's passed by referencee or by value // to the end user. if !is_interface { // Dereference if got_ptr && !exp_ptr { p.cgen.set_placeholder(ph, '*') } // Reference // TODO ptr hacks. DOOM hacks, fix please. if !got_ptr && exp_ptr && got != 'voidptr' { // Special case for mutable arrays. We can't `&` function // results, // have to use `(array[]){ expr }` hack. if expected.starts_with('array_') && exp_ptr { //&& !arg.is_mut{ p.cgen.set_placeholder(ph, '& /*111*/ (array[]){') p.gen('}[0] ') } // println('\ne:"$expected" got:"$got"') else if ! (expected == 'void*' && got == 'int') && ! (expected == 'byte*' && got.contains(']byte')) && ! (expected == 'byte*' && got == 'string') && //! (expected == 'void*' && got == 'array_int') { ! (expected == 'byte*' && got == 'byteptr') { p.cgen.set_placeholder(ph, '& /*112 EXP:"$expected" GOT:"$got" */') } } } else if is_interface { if !got_ptr { 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 if p.tok != .comma && !f.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 && (!f.is_variadic || (f.is_variadic && i < f.args.len-2 )) { p.check(.comma) p.gen(',') } } } // varargs varg_type, varg_values := p.fn_call_vargs(f) if f.is_variadic { saved_args << varg_type } if p.tok == .comma { p.error('wrong number of arguments for fn `$f.name`: expected $f.args.len, but got more') } p.check(.rpar) if f.is_generic { type_map := p.extract_type_inst(f, saved_args) p.dispatch_generic_fn_instance(mut f, type_map) } if f.is_variadic { p.fn_gen_caller_vargs(f, varg_type, varg_values) } } // From a given generic function and an argument list matching its signature, // create a type instantiation fn (p mut Parser) extract_type_inst(f &Fn, args_ []string) TypeInst { mut r := TypeInst{} mut i := 0 mut args := args_ if f.typ != 'void' { args << f.typ } for e in args { if e == '' { continue } tp := f.type_pars[i] mut ti := e if ti.starts_with('fn (') { fn_args := ti[4..].all_before(') ').split(',') mut found := false for fa_ in fn_args { mut fa := fa_ for fa.starts_with('array_') { fa = fa[6..] } if fa == tp { r.inst[tp] = fa found = true i += 1 break } } if found { continue } ti = ti.all_after(') ') } for ti.starts_with('array_') { ti = ti[6..] } if r.inst[tp] != '' { if r.inst[tp] != ti { p.error('type parameter `$tp` has type ${r.inst[tp]}, not `$ti`') } continue } // println("extracted $tp => $ti") r.inst[tp] = ti i += 1 if i >= f.type_pars.len { break } } if r.inst[f.typ] == '' && f.typ in f.type_pars { r.inst[f.typ] = '_ANYTYPE_' } for tp in f.type_pars { if r.inst[tp] == '' { p.error_with_token_index('unused type parameter `$tp`', f.body_idx-2) } } return r } // Replace type params of a given generic function using a TypeInst fn (p mut Parser) replace_type_params(f &Fn, ti TypeInst) []string { mut sig := []string for a in f.args { sig << a.typ } sig << f.typ mut r := []string for _, a in sig { mut fi := a mut fr := '' if fi.starts_with('fn (') { fr += 'fn (' mut fn_args := fi[4..].all_before(') ').split(',') fn_args << fi.all_after(') ') for i, fa_ in fn_args { mut fna := fa_.trim_space() for fna.starts_with('array_') { fna = fna[6..] fr += 'array_' } if fna in ti.inst.keys() { fr += ti.inst[fna] } else { fr += fna } if i <= fn_args.len-3 { fr += ',' } else if i == fn_args.len-2 { fr += ') ' } } r << fr continue } for fi.starts_with('array_') { fi = fi[6..] fr += 'array_' } if fi.starts_with('varg_') { fi = fi[5..] fr += 'varg_' } if fi in ti.inst.keys() { mut t := ti.inst[fi] fr += t // println("replaced $a => $fr") } else { fr += fi } r << fr } return r } fn (p mut Parser) register_vargs_stuct(typ string, len int) string { vargs_struct := 'varg_$typ' varg_type := Type{ cat: .struct_, name: vargs_struct, mod: p.mod } mut varg_len := len if !p.table.known_type(vargs_struct) { p.table.register_type2(varg_type) p.cgen.typedefs << 'typedef struct $vargs_struct $vargs_struct;\n' } else { ex_typ := p.table.find_type(vargs_struct) ex_len := ex_typ.fields[1].name[5..ex_typ.fields[1].name.len-1].int() if ex_len > varg_len { varg_len = ex_len } p.table.rewrite_type(varg_type) } p.table.add_field(vargs_struct, 'len', 'int', false, '', .public) p.table.add_field(vargs_struct, 'args[$varg_len]', typ, false, '', .public) return vargs_struct } fn (p mut Parser) fn_call_vargs(f Fn) (string, []string) { if !f.is_variadic { return '', []string } last_arg := f.args.last() mut varg_def_type := last_arg.typ[3..] mut types := []string mut values := []string for p.tok != .rpar { if p.tok == .comma { p.check(.comma) } p.cgen.start_tmp() mut varg_type := p.bool_expression() varg_value := p.cgen.end_tmp() if varg_type.starts_with('varg_') && (values.len > 0 || p.tok == .comma) { p.error('You cannot pass additional vargs when forwarding vargs to another function/method') } if !f.is_generic { p.check_types(last_arg.typ, varg_type) } else { if types.len > 0 { for t in types { p.check_types(varg_type, t) } } } ref_deref := if last_arg.typ.ends_with('*') && !varg_type.ends_with('*') { '&' } else if !last_arg.typ.ends_with('*') && varg_type.ends_with('*') { '*' } else { '' } types << varg_type values << '$ref_deref$varg_value' } for va in p.table.varg_access { if va.fn_name != f.name { continue } if va.index >= values.len { p.error_with_token_index('variadic arg index out of range: $va.index/${values.len-1}, vargs are 0 indexed', va.tok_idx) } } if !f.is_method && f.args.len > 1 { p.cgen.gen(',') } return types[0], values } fn (p mut Parser) fn_gen_caller_vargs(f &Fn, varg_type string, values []string) { is_varg := varg_type.starts_with('varg_') if is_varg { // forwarding varg p.cgen.gen('${values[0]}') } else { vargs_struct := p.register_vargs_stuct(varg_type, values.len) p.cgen.gen('&($vargs_struct){.len=$values.len,.args={'+values.join(',')+'}}') } } fn (p mut Parser) register_multi_return_stuct(types []string) string { typ := '_V_MulRet_' + types.join('_V_').replace('*', '_PTR_') if p.table.known_type(typ) { return typ } p.table.register_type2(Type{ cat: .struct_, name: typ, mod: p.mod }) for i, t in typ.replace('_V_MulRet_', '').replace('_PTR_', '*').split('_V_') { p.table.add_field(typ, 'var_$i', t, false, '', .public) } p.cgen.typedefs << 'typedef struct $typ $typ;' return typ } fn (p mut Parser) rename_generic_fn_instance(f mut Fn, ti TypeInst) { if f.is_method { f.name = f.receiver_typ + '_' + f.name } f.name = f.name + '_T' for k in ti.inst.keys() { f.name = f.name + '_' + type_to_safe_str(ti.inst[k]) } } fn (p mut Parser) dispatch_generic_fn_instance(f mut Fn, ti TypeInst) { mut new_inst := true for e in f.type_inst { if e.inst.str() == ti.inst.str() { new_inst = false break } } if !new_inst { p.rename_generic_fn_instance(mut f, ti) _f := p.table.find_fn(f.name) or { p.error('function instance `$f.name` not found') return } f.args = _f.args f.typ = _f.typ f.is_generic = false f.type_inst = []TypeInst f.dispatch_of = ti // println('using existing inst $f.name(${f.str_args(p.table)}) $f.typ') return } f.type_inst << ti p.table.register_fn(f) // Remember current scanner position, go back here for each type instance // TODO remove this once tokens are cached in `new_parser()` saved_tok_idx := p.cur_tok_index() saved_fn := p.cur_fn saved_var_idx := p.var_idx saved_local_vars := p.local_vars p.clear_vars() saved_line := p.cgen.cur_line saved_lines := p.cgen.lines saved_is_tmp := p.cgen.is_tmp saved_tmp_line := p.cgen.tmp_line returns := p.returns // should be always false p.rename_generic_fn_instance(mut f, ti) f.is_generic = false // the instance is a normal function f.type_inst = []TypeInst f.scope_level = 0 f.dispatch_of = ti // TODO this is done to prevent a crash as a result of this not being // properly initialised. This is a bug somewhere futher upstream f.defer_text = []string old_args := f.args new_types := p.replace_type_params(f, ti) f.args = []Var for i in 0..new_types.len-1 { mut v := old_args[i] v.typ = new_types[i] f.args << v } f.typ = new_types.last() if f.typ in f.type_pars { f.typ = '_ANYTYPE_' } if f.typ in ti.inst { f.typ = ti.inst[f.typ] } if f.is_method { p.add_method(f.args[0].name, f) } else { p.table.register_fn(f) } // println("generating gen inst $f.name(${f.str_args(p.table)}) $f.typ : $ti.inst") p.cgen.is_tmp = false p.returns = false p.cgen.tmp_line = '' p.cgen.cur_line = '' p.cgen.lines = []string p.cur_fn = *f for arg in f.args { p.register_var(arg) } p.token_idx = f.body_idx-1 p.next() // re-initializes the parser properly str_args := f.str_args(p.table) p.in_dispatch = true p.genln('${p.get_linkage_prefix()}$f.typ $f.name($str_args) {') // p.genln('/* generic fn instance $f.name : $ti.inst */') p.statements() p.in_dispatch = false if f.typ == '_ANYTYPE_' { f.typ = p.cur_fn.typ f.name = f.name.replace('_ANYTYPE_', type_to_safe_str(f.typ)) p.cgen.lines[0] = p.cgen.lines[0].replace('_ANYTYPE_', f.typ) p.table.register_fn(f) } for l in p.cgen.lines { p.cgen.fns << l } p.token_idx = saved_tok_idx-1 p.next() p.check(.rpar) // end of the arg list which caused this dispatch p.cur_fn = saved_fn p.var_idx = saved_var_idx p.local_vars = saved_local_vars p.cgen.lines = saved_lines p.cgen.cur_line = saved_line p.cgen.is_tmp = saved_is_tmp p.cgen.tmp_line = saved_tmp_line p.returns = false } // "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[1..] { s += ', $a.typ' } } s += ')' } } else if arg.typ.starts_with('varg_') { s += '$arg.typ *$arg.name' } 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 } // find local function variable with closest name to `name` fn (p &Parser) find_misspelled_local_var(name string, min_match f32) string { mut closest := f32(0) mut closest_var := '' for var in p.local_vars { if var.scope_level > p.cur_fn.scope_level { continue } n := name.all_after('.') if var.name == '' || (n.len - var.name.len > 2 || var.name.len - n.len > 2) { continue } c := strings.dice_coefficient(var.name, n) if c > closest { closest = c closest_var = var.name } } return if closest >= min_match { closest_var } else { '' } } fn (fns []Fn) contains(f Fn) bool { for ff in fns { if ff.name == f.name { return true } } return false } pub fn (f Fn) v_fn_module() string { return f.mod } pub fn (f Fn) v_fn_name() string { return f.name.replace('${f.mod}__', '') }