1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00
v/vlib/compiler/fn.v
2019-11-23 10:06:14 +03:00

1617 lines
43 KiB
V

// 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
generic_tmpl []Token
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 = []
}
}
// 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
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.fspace()
}
p.returns = false
//p.gen('/* returns $p.returns */')
p.next()
p.fspace()
// Method receiver
mut receiver_typ := ''
if p.tok == .lpar {
f.is_method = true
p.check(.lpar)
receiver_name := p.check_name()
p.fspace()
is_mut := p.tok == .key_mut
is_amp := p.tok == .amp
if is_mut || is_amp {
p.check(p.tok)
if !is_amp {
p.fspace()
}
}
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 {
//if f.name == 'main' {
//println('\n\nfn_decl() name=$f.name pass=$p.pass $p.file_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') && !p.is_vgen {
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.fspace()
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.fspace()
p.check(.lcbr)
//p.fgen_nl()
}
// Register ?option type for return value and args
if typ.starts_with('Option_') {
p.cgen.typedefs << 'typedef Option $typ;'
}
for arg in f.args {
if arg.typ.starts_with('Option_') {
p.cgen.typedefs << 'typedef Option $arg.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() {
p.save_generic_tmpl(mut f, p.cur_tok_index())
if f.is_method {
rcv := p.table.find_type(receiver_typ)
if p.first_pass() && rcv.name == '' {
p.error('cannot currently add generic method to a type declared after it or in another module')
}
// println('added generic method r:$rcv.name f:$f.name')
p.add_method(rcv.name, f)
} else {
p.table.register_fn(f)
}
}
p.set_current_fn( EmptyFn )
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.fgen_nl()
}
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<Bar>()` 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 = 'DWORD WINAPI'
}
wrapper_text := '$wrapper_type $wrapper_name($arg_struct_name * arg) {$fn_name( /*f*/$str_args ); return 0; }'
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, (LPTHREAD_START_ROUTINE)$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()
mut receiver_is_interface := false
if receiver.typ.ends_with('er') {
// I absolutely love this syntax
// `s.speak()` =>
// `((void (*)())(Speaker_name_table[s._interface_idx][1]))(s._object);
// where `1` refers to the speak method, since it's the second method
// of the Speaker interface
t := p.table.find_type(receiver.typ)
if t.cat == .interface_ {
// Find the index of the method
mut idx := 0
for i, method in t.methods {
if method.name == f.name {
idx = i
}
}
p.cgen.resetln('')
var := p.expr_var.name
iname := f.args[0].typ // Speaker
p.gen('(($f.typ (*)())(${iname}_name_table[${var}._interface_idx][$idx]))(${var}._object')
receiver_is_interface = true
}
}
//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)
}
if !receiver_is_interface {
p.gen_method_call(receiver, receiver_type, cgen_name, f.typ, method_ph)
}
} else {
// Normal function call
p.gen('$cgen_name (')
}
// `foo<Bar>()`
// 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')
}
//if !is_interface {
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
// update the Fn object's args[]
fn (p mut Parser) fn_args(f mut Fn) {
p.check(.lpar)
defer { p.check(.rpar) }
if f.is_interface {
interface_arg := Var {
typ: f.receiver_typ
token_idx: p.cur_tok_index()
}
f.args << interface_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: `fn foo(n mut int) {` => `fn 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.fspace()
}
}
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(', ')
}
//if f.args[0].typ.ends_with('*') {
//p.gen('&/*119*/')
//}
/*
pos := p.cgen.cur_line.index('/* ? */')
if pos > -1 {
expr := p.cgen.cur_line[pos..]
// TODO hack
// If current expression is a func call, generate the array hack
if expr.contains('(') {
p.cgen.set_placeholder(pos, '(${arg.typ[..arg.typ.len-1]}[]){')
p.gen('}[0] ')
}
}
*/
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 {
p.error('not enough arguments in call to `${f.str_for_error()}`')
}
// 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 {
p.mutable_arg_error(i, arg, f)
}
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)
p.fspace()
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(')
}
// x64 println gen
if p.pref.x64 && i == 0 && f.name == 'println' && p.tok == .str && p.peek() == .rpar {
p.x64.gen_print(p.lit)
}
mut typ := p.bool_expression()
// Register an interface type usage:
// fn run(r Animal) { ... }
// `run(dog)` adds `Dog` to the `Animal` interface.
// This is needed to generate an interface table.
if arg.typ.ends_with('er') {
t := p.table.find_type(arg.typ)
if t.cat == .interface_ {
// perform((Speaker) { ._object = &dog,
// _interface_idx = _Speaker_Dog_index })
p.cgen.set_placeholder(ph, '($arg.typ) { ._object = &')
p.gen(', ._interface_idx = _${arg.typ}_${typ}_index} /* i. arg*/')
p.table.add_gen_type(arg.typ, typ)
}
}
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] ')
}
else if exp_ptr && expected == got + '*' {
$if !tinyc {
expr := p.cgen.cur_line[ph..]
// TODO hack
// If current expression is a func call, generate the array hack
if expr.contains('(') {
//println('fn hack expr=$expr')
p.cgen.set_placeholder(ph, '& /*113 e="$expected" g="$got"*/ ($got[]){')
p.gen('}[0] ')
} else {
p.cgen.set_placeholder(ph, '& /*114*/')
}
} $else {
p.cgen.set_placeholder(ph, '& /*114*/')
}
}
// 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.pref.is_bare {
p.cgen.set_placeholder(ph, '& /*112 e="$expected" g="$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 in call to `${f.str_for_error()}`')
}
if p.tok == .comma && (!f.is_variadic || (f.is_variadic && i < f.args.len-2 )) {
p.check(.comma)
p.fspace()
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 in call to `${f.str_for_error()}`')
}
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)
p.error('unused type parameter `$tp`')
}
}
return r
}
// replace a generic type using TypeInst
fn replace_generic_type(gen_type string, ti &TypeInst) string {
mut typ := gen_type.replace('map_', '')
.replace('varg_', '').trim_right('*')
for typ.starts_with('array_') { typ = typ[6..] }
if typ in ti.inst {
typ = gen_type.replace(typ, ti.inst[typ])
return typ
}
typ = gen_type
if typ.starts_with('fn (') {
args := typ[4..].all_before_last(')').split(',')
ret_t := typ.all_after(')').trim_space()
mut args_r := []string
for arg in args {
args_r << replace_generic_type(arg, ti)
}
mut t := 'fn (' + args_r.join(',') + ')'
if ret_t.len > 0 {
t += ' ' + replace_generic_type(ret_t, ti)
}
typ = t
}
return typ
}
// replace return type & param types for a given generic function using TypeInst
fn replace_generic_type_params(f mut Fn, ti &TypeInst) {
mut args := []Var
for i, _ in f.args {
mut arg := f.args[i]
arg.typ = replace_generic_type(arg.typ, ti)
args << arg
}
f.args = args
f.typ = replace_generic_type(f.typ, ti)
}
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)
}
varg_type, varg_value := p.tmp_expr()
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
}
// save the tokens for the generic funciton body (between `{}`)
// the function signature isn't saved, it is reconstructed from Fn
fn (p mut Parser) save_generic_tmpl(f mut Fn, pos int) {
mut cbr_depth := 1
mut tokens := []Token
for i in pos..p.tokens.len-1 {
tok := p.tokens[i]
if tok.tok == .lcbr { cbr_depth++ }
if tok.tok == .rcbr {
cbr_depth--
if cbr_depth == 0 { break }
}
tokens << tok
}
f.generic_tmpl = tokens
}
// replace generic types in function body template with types from TypeInst
fn (f &Fn) generic_tmpl_to_inst(ti &TypeInst) string {
mut fn_body := ''
for tok in f.generic_tmpl {
mut tok_str := tok.str()
if tok.tok == .name && tok_str in ti.inst {
tok_str = ti.inst[tok_str]
}
fn_body += ' $tok_str'
}
return fn_body
}
fn 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 {
rename_generic_fn_instance(mut f, ti)
_f := p.table.find_fn(f.name) or {
p.error('function instance `$f.name` not found')
return
}
// println('using existing inst ${p.fn_signature_v(f)}')
return
}
f.type_inst << *ti
p.table.register_fn(f)
rename_generic_fn_instance(mut f, ti)
replace_generic_type_params(mut f, ti)
// TODO: save dispatch info when update to incremental parsing
f.dispatch_of = *ti
// TODO: Handle case where type not defined yet, see above
// 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 {
// TODO: add_method won't add anything on second pass
// p.add_method(f.args[0].typ.trim_right('*'), f)
} else {
p.table.register_fn(f)
}
mut fn_code := '${p.fn_signature_v(f)} {\n${f.generic_tmpl_to_inst(ti)}\n}'
// TODO: parse incrementally as needed & set typeinst
if f.mod in p.v.gen_parser_idx {
pidx := p.v.gen_parser_idx[f.mod]
p.v.parsers[pidx].add_text(fn_code)
for mod in p.table.imports {
if p.v.parsers[pidx].import_table.known_import(mod) { continue }
p.v.parsers[pidx].register_import(mod, 0)
}
} else {
// TODO: add here after I work out bug
}
p.cgen.fns << '${p.fn_signature(f)};'
}
// "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 += ')'
}
}
*/
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
}
fn (f &Fn) str_args_v(table &Table) string {
mut str_args := ''
for i, arg in f.args {
if f.is_method && i == 0 { continue }
mut arg_typ := arg.typ.replace('array_', '[]').replace('map_', 'map[string]')
if arg_typ == 'void*' { arg_typ = 'voidptr' } else if arg_typ == 'byte*' { arg_typ = 'byteptr' }
if arg.is_mut { arg_typ = 'mut '+arg_typ.trim('*') }
else if arg_typ.ends_with('*') || arg.ptr { arg_typ = '&'+arg_typ.trim_right('*') }
str_args += '$arg.name $arg_typ'
if i < f.args.len-1 { str_args += ','}
}
return str_args
}
// 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
}
fn (p &Parser) fn_signature(f &Fn) string {
return '$f.typ $f.name(${f.str_args(p.table)})'
}
fn (p &Parser) fn_signature_v(f &Fn) string {
mut method := ''
mut f_name := f.name.all_after('__')
if f.is_method {
receiver_arg := f.args[0]
receiver_type := receiver_arg.typ.trim('*')
f_name = f_name.all_after('${receiver_type}_')
mut rcv_typ := receiver_arg.typ.replace('array_', '[]').replace('map_', 'map[string]')
if receiver_arg.is_mut { rcv_typ = 'mut '+rcv_typ.trim('*') }
else if rcv_typ.ends_with('*') || receiver_arg.ptr { rcv_typ = '&'+rcv_typ.trim_right('&*') }
method = '($receiver_arg.name $rcv_typ) '
}
vis := if f.is_public { 'pub ' } else { '' }
f_type := if f.typ == 'void' { '' } else if f.typ == 'void*' { 'voidptr' }
else if f.typ == 'byte*' { 'byteptr' } else { f.typ }
return '${vis}fn $method$f_name(${f.str_args_v(p.table)}) $f_type'
}
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}__', '')
}
pub fn (f &Fn) str_for_error() string {
// Build the args for the error
mut s := ''
for i, a in f.args {
if i == 0 {
if f.is_method {
s += a.typ + '.' + f.name + '('
continue
}
s += f.name + '('
}
s += a.typ
if i < f.args.len - 1 {
s += ', '
}
}
return s + ')'
}