mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
907 lines
24 KiB
Go
907 lines
24 KiB
Go
// 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
|
|
}
|
|
|
|
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_run() && !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_run() && !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_run() && 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 {
|
|
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)
|
|
is_generic = true
|
|
}
|
|
// 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)
|
|
// println('FN .decL $f.name typ=$f.typ str_args="$str_args"')
|
|
// 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'
|
|
}
|
|
|
|
mut dll_export_linkage := ''
|
|
|
|
if p.os == .msvc && p.attr == 'live' && p.pref.is_so {
|
|
dll_export_linkage = '__declspec(dllexport) '
|
|
}
|
|
|
|
// Only in C code generate User_register() instead of register()
|
|
// Internally it's still stored as "register" in type User
|
|
// mut fn_name_cgen := f.name
|
|
// if receiver_typ != '' {
|
|
// fn_name_cgen = '${receiver_typ}_$f.name'
|
|
// fn_name_cgen = fn_name_cgen.replace(' ', '')
|
|
// fn_name_cgen = fn_name_cgen.replace('*', '')
|
|
// }
|
|
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')
|
|
}
|
|
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')
|
|
}
|
|
p.cur_fn = f
|
|
// 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_run() && 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_run() || is_live || is_fn_header || skip_main_in_test {
|
|
// First pass? Skip the body for now [BIG]
|
|
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()
|
|
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_run() && p.mod == 'main' {
|
|
//println('ADDING SO FN $fn_name_cgen')
|
|
p.cgen.so_fns << fn_name_cgen
|
|
fn_name_cgen = '(* $fn_name_cgen )'
|
|
}
|
|
// Actual fn declaration!
|
|
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_run() {
|
|
// 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
|
|
}
|
|
// We are in profile 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;'
|
|
}
|
|
p.statements_no_rcbr()
|
|
// 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' {
|
|
p.genln('}')
|
|
return
|
|
}
|
|
p.check_unused_variables()
|
|
p.cur_fn = EmptyFn
|
|
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()`')
|
|
}
|
|
}
|
|
cgen_name := p.table.cgen_name(f)
|
|
// 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')
|
|
}
|
|
p.next()
|
|
p.fn_call_args(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_run() && !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 *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
|
|
}
|
|
// 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)
|
|
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.create_type_string(T, name)
|
|
p.cgen.cur_line.replace(typ, '')
|
|
p.next()
|
|
return p.fn_call_args(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 contains_capital(s string) bool {
|
|
// for c in s {
|
|
for i := 0; i < s.len; i++ {
|
|
c := s[i]
|
|
if c >= `A` && c <= `Z` {
|
|
return true
|
|
}
|
|
}
|
|
return 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*) '
|
|
}
|
|
}
|
|
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
|
|
}
|