diff --git a/compiler/cgen.v b/compiler/cgen.v index e37c17ae20..c675c9a6f5 100644 --- a/compiler/cgen.v +++ b/compiler/cgen.v @@ -63,7 +63,7 @@ fn (g mut CGen) genln(s string) { g.cur_line = '$g.cur_line $s' if g.cur_line != '' { if g.line_directives && g.cur_line.trim_space() != '' { - g.lines << '#line $g.line "$g.file"' + g.lines << '\n#line $g.line "$g.file"' } g.lines << g.cur_line g.prev_line = g.cur_line diff --git a/compiler/fn.v b/compiler/fn.v index 89fb83a57c..654677fd99 100644 --- a/compiler/fn.v +++ b/compiler/fn.v @@ -5,7 +5,6 @@ module main import( - os strings ) @@ -34,6 +33,7 @@ mut: is_decl bool // type myfn fn(int, int) defer_text []string //gen_types []string + fn_name_sp ScannerPos } fn (p &Parser) find_var(name string) ?Var { @@ -215,6 +215,7 @@ fn (p mut Parser) fn_decl() { else { f.name = p.check_name() } + f.fn_name_sp = p.scanner.get_scanner_pos() // 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 @@ -244,7 +245,7 @@ fn (p mut Parser) fn_decl() { } // full mod function name // os.exit ==> os__exit() - if !is_c && !p.builtin_mod && p.mod != 'main' && receiver_typ.len == 0 { + if !is_c && !p.builtin_mod && receiver_typ.len == 0 { f.name = p.prepend_mod(f.name) } if p.first_pass() && receiver_typ.len == 0 { @@ -318,14 +319,12 @@ fn (p mut Parser) fn_decl() { } // Register function f.typ = typ - mut str_args := f.str_args(p.table) + str_args := f.str_args(p.table) // Special case for main() args - if f.name == 'main' && !has_receiver { + if f.name == 'main__main' && !has_receiver { if str_args != '' || typ != 'void' { - p.error('fn main must have no arguments and no return values') + p.error_with_position('fn main must have no arguments and no return values', f.fn_name_sp) } - typ = 'int' - str_args = 'int argc, char** argv' } dll_export_linkage := if p.os == .msvc && p.attr == 'live' && p.pref.is_so { '__declspec(dllexport) ' @@ -341,7 +340,7 @@ fn (p mut Parser) fn_decl() { // 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 := f.name == 'main' && p.pref.is_test + skip_main_in_test := false if !is_c && !is_live && !is_sig && !is_fn_header && !skip_main_in_test { if p.pref.obfuscate { p.genln('; // $f.name') @@ -434,7 +433,7 @@ fn (p mut Parser) fn_decl() { fn_decl += '; // $f.name' } // Add function definition to the top - if !is_c && f.name != 'main' && p.first_pass() { + if !is_c && p.first_pass() { // TODO hack to make Volt compile without -embed_vlib if f.name == 'darwin__nsstring' && p.pref.build_mode == .default_mode { return @@ -447,37 +446,10 @@ fn (p mut Parser) fn_decl() { //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 'os' in p.table.imports { - if f.name == 'main' { - p.genln('os__args = os__init_os_args(argc, (byteptr*)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 := os.filename(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 f.name == 'main__main' || f.name == 'main' || f.name == 'WinMain' { if p.pref.is_test && !p.scanner.file_path.contains('/volt') { - p.error('tests cannot have function `main`') + p.error_with_position('tests cannot have function `main`', f.fn_name_sp) } } // println('is_c=$is_c name=$f.name') @@ -486,7 +458,7 @@ _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0); return } // Profiling mode? Start counting at the beginning of the function (save current time). - if p.pref.is_prof && f.name != 'main' && f.name != 'time__ticks' { + 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 { @@ -509,8 +481,8 @@ _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0); p.genln(f.defer_text[f.scope_level]) } } - if typ != 'void' && !p.returns && f.name != 'main' && f.name != 'WinMain' { - p.error('$f.name must return "$typ"') + if typ != 'void' && !p.returns { + p.error_with_position('$f.name must return "$typ"', f.fn_name_sp) } if p.attr == 'live' && p.pref.is_so { //p.genln('// live_function body end') diff --git a/compiler/gen_js.v b/compiler/gen_js.v index 105f809a54..6719cd8f71 100644 --- a/compiler/gen_js.v +++ b/compiler/gen_js.v @@ -36,7 +36,7 @@ fn (p mut Parser) gen_fn_decl(f Fn, typ, _str_args string) { fn (p mut Parser) gen_blank_identifier_assign() { p.check_name() p.check_space(.assign) - typ := p.bool_expression() + p.bool_expression() or_else := p.tok == .key_orelse //tmp := p.get_tmp() if or_else { diff --git a/compiler/live.v b/compiler/live.v index 6cd788af9b..7d5fce9763 100644 --- a/compiler/live.v +++ b/compiler/live.v @@ -1,9 +1,10 @@ module main import os +import time fn (v &V) generate_hotcode_reloading_compiler_flags() []string { - mut a := []string + mut a := []string if v.pref.is_live || v.pref.is_so { // See 'man dlopen', and test running a GUI program compiled with -live if (v.os == .linux || os.user_os() == 'linux'){ @@ -13,11 +14,11 @@ fn (v &V) generate_hotcode_reloading_compiler_flags() []string { a << '-flat_namespace' } } - return a + return a } fn (v &V) generate_hotcode_reloading_declarations() { - mut cgen := v.cgen + mut cgen := v.cgen if v.os != .windows && v.os != .msvc { if v.pref.is_so { cgen.genln('pthread_mutex_t live_fn_mutex;') @@ -35,9 +36,33 @@ fn (v &V) generate_hotcode_reloading_declarations() { } } -fn (v &V) generate_hot_reload_code() { - mut cgen := v.cgen +fn (v &V) generate_hotcode_reloading_main_caller() { + if !v.pref.is_live { return } + // We are in live code reload mode, so start the .so loader in the background + mut cgen := v.cgen + cgen.genln('') + file_base := os.filename(v.dir).replace('.v', '') + if !(v.os == .windows || v.os == .msvc) { + // unix: + so_name := file_base + '.so' + cgen.genln(' char *live_library_name = "$so_name";') + cgen.genln(' load_so(live_library_name);') + cgen.genln(' pthread_t _thread_so;') + cgen.genln(' pthread_create(&_thread_so , NULL, &reload_so, live_library_name);') + } else { + // windows: + so_name := file_base + if v.os == .msvc {'.dll'} else {'.so'} + cgen.genln(' char *live_library_name = "$so_name";') + cgen.genln(' live_fn_mutex = CreateMutexA(0, 0, 0);') + cgen.genln(' load_so(live_library_name);') + cgen.genln(' unsigned long _thread_so;') + cgen.genln(' _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0);') + } +} +fn (v &V) generate_hot_reload_code() { + mut cgen := v.cgen + // Hot code reloading if v.pref.is_live { mut file := os.realpath(v.dir) @@ -46,24 +71,33 @@ fn (v &V) generate_hot_reload_code() { // Need to build .so file before building the live application // The live app needs to load this .so file on initialization. mut vexe := os.args[0] - + if os.user_os() == 'windows' { vexe = vexe.replace('\\', '\\\\') file = file.replace('\\', '\\\\') } - + mut msvc := '' if v.os == .msvc { msvc = '-os msvc' } - + mut debug := '' - + if v.pref.is_debug { debug = '-debug' } - - os.system('$vexe $msvc $debug -o $file_base -shared $file') + + cmd_compile_shared_library := '$vexe $msvc $debug -o $file_base -shared $file' + if v.pref.show_c_cmd { + println(cmd_compile_shared_library) + } + ticks := time.ticks() + os.system(cmd_compile_shared_library) + diff := time.ticks() - ticks + println('compiling shared library took $diff ms') + println('=========\n') + cgen.genln(' void lfnmutex_print(char *s){ diff --git a/compiler/main.v b/compiler/main.v index 67226a3385..b3144e8836 100644 --- a/compiler/main.v +++ b/compiler/main.v @@ -368,7 +368,7 @@ fn (v mut V) compile() { } } $if js { - cgen.genln('main();') + cgen.genln('main__main();') } cgen.save() v.cc() @@ -439,35 +439,53 @@ string _STR_TMP(const char *fmt, ...) { // It can be skipped in single file programs if v.pref.is_script { //println('Generating main()...') - cgen.genln('int main() { init_consts();') + v.gen_main_start(true) cgen.genln('$cgen.fn_main;') - cgen.genln('return 0; }') + v.gen_main_end('return 0') } else { - println('panic: function `main` is undeclared in the main module') - exit(1) + verror('function `main` is not declared in the main module') } } else if v.pref.is_test { if v.table.main_exists() { verror('test files cannot have function `main`') - } - // make sure there's at least on test function + } if !v.table.has_at_least_one_test_fn() { verror('test files need to have at least one test function') - } - // Generate `main` which calls every single test function - cgen.genln('int main() { init_consts();') + } + // Generate a C `main`, which calls every single test function + v.gen_main_start(false) for _, f in v.table.fns { - if f.name.starts_with('test_') { + if f.name.starts_with('main__test_') { cgen.genln('$f.name();') } } - cgen.genln('return g_test_ok == 0; }') + v.gen_main_end('return g_test_ok == 0') + } + else if v.table.main_exists() { + v.gen_main_start(true) + cgen.genln(' main__main();') + v.gen_main_end('return 0') } } } +fn (v mut V) gen_main_start(add_os_args bool){ + v.cgen.genln('int main(int argc, char** argv) { ') + v.cgen.genln(' init_consts();') + if add_os_args && 'os' in v.table.imports { + v.cgen.genln(' os__args = os__init_os_args(argc, (byteptr*)argv);') + } + v.generate_hotcode_reloading_main_caller() + v.cgen.genln('') +} +fn (v mut V) gen_main_end(return_statement string){ + v.cgen.genln('') + v.cgen.genln(' $return_statement;') + v.cgen.genln('}') +} + fn final_target_out_name(out_name string) string { mut cmd := if out_name.starts_with('/') { out_name diff --git a/compiler/parser.v b/compiler/parser.v index 9f77bc4efd..76f3eb7631 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -2037,7 +2037,10 @@ fn (p mut Parser) dot(str_typ string, method_ph int) string { // field if has_field { struct_field := if typ.name != 'Option' { p.table.var_cgen_name(field_name) } else { field_name } - field := p.table.find_field(typ, struct_field) or { panic('field') } + field := p.table.find_field(typ, struct_field) or { + p.error('missing field: $struct_field in type $typ.name') + exit(1) + } if !field.is_mut && !p.has_immutable_field { p.has_immutable_field = true p.first_immutable_field = field @@ -3008,7 +3011,10 @@ fn (p mut Parser) struct_init(typ string) string { if field in inited_fields { p.error('already initialized field `$field` in `$t.name`') } - f := t.find_field(field) or { panic('field') } + f := t.find_field(field) or { + p.error('no such field: "$field" in type $typ') + break + } inited_fields << field p.gen_struct_field_init(field) p.check(.colon) @@ -3774,23 +3780,35 @@ fn (p &Parser) prepend_mod(name string) string { fn (p mut Parser) go_statement() { p.check(.key_go) + mut gopos := p.scanner.get_scanner_pos() // TODO copypasta of name_expr() ? - // Method if p.peek() == .dot { + // Method var_name := p.lit - v := p.find_var(var_name) or { return } + v := p.find_var(var_name) or { + return + } p.mark_var_used(v) + gopos = p.scanner.get_scanner_pos() p.next() p.check(.dot) typ := p.table.find_type(v.typ) - method := p.table.find_method(typ, p.lit) or { panic('go method') } + method := p.table.find_method(typ, p.lit) or { + p.error_with_position('go method missing $var_name', gopos) + return + } p.async_fn_call(method, 0, var_name, v.typ) } - // Normal function else { - f := p.table.find_fn(p.lit) or { panic('fn') } + f_name := p.lit + // Normal function + f := p.table.find_fn(p.prepend_mod(f_name)) or { + println( p.table.debug_fns() ) + p.error_with_position('can not find function $f_name', gopos) + return + } if f.name == 'println' || f.name == 'print' { - p.error('`go` cannot be used with `println`') + p.error_with_position('`go` cannot be used with `println`', gopos) } p.async_fn_call(f, 0, '', '') } diff --git a/compiler/scanner.v b/compiler/scanner.v index f418a91c3e..f9404e5843 100644 --- a/compiler/scanner.v +++ b/compiler/scanner.v @@ -645,6 +645,17 @@ fn (s &Scanner) error_with_col(msg string, col int) { column := col-1 linestart := s.find_current_line_start_position() lineend := s.find_current_line_end_position() + + fullpath := os.realpath( s.file_path ) + // The filepath:line:col: format is the default C compiler + // error output format. It allows editors and IDE's like + // emacs to quickly find the errors in the output + // and jump to their source with a keyboard shortcut. + // Using only the filename leads to inability of IDE/editors + // to find the source file, when it is in another folder. + //println('${s.file_path}:${s.line_nr + 1}:${column+1}: $msg') + println('${fullpath}:${s.line_nr + 1}:${column+1}: $msg') + if s.should_print_line_on_error && lineend > linestart { line := s.text.substr( linestart, lineend ) // The pointerline should have the same spaces/tabs as the offending @@ -661,16 +672,7 @@ fn (s &Scanner) error_with_col(msg string, col int) { println(line) println(pointerline) } - fullpath := os.realpath( s.file_path ) - _ = fullpath - // The filepath:line:col: format is the default C compiler - // error output format. It allows editors and IDE's like - // emacs to quickly find the errors in the output - // and jump to their source with a keyboard shortcut. - // Using only the filename leads to inability of IDE/editors - // to find the source file, when it is in another folder. - //println('${s.file_path}:${s.line_nr + 1}:${column+1}: $msg') - println('${fullpath}:${s.line_nr + 1}:${column+1}: $msg') + exit(1) } diff --git a/compiler/table.v b/compiler/table.v index aa1b62e361..f7cda982c2 100644 --- a/compiler/table.v +++ b/compiler/table.v @@ -698,7 +698,7 @@ fn (table &Table) is_interface(name string) bool { // Do we have fn main()? fn (t &Table) main_exists() bool { for _, f in t.fns { - if f.name == 'main' { + if f.name == 'main__main' { return true } } @@ -707,7 +707,7 @@ fn (t &Table) main_exists() bool { fn (t &Table) has_at_least_one_test_fn() bool { for _, f in t.fns { - if f.name.starts_with('test_') { + if f.name.starts_with('main__test_') { return true } } diff --git a/examples/hot_reload/graph.v b/examples/hot_reload/graph.v index 38f56d3a29..8acc3019b5 100644 --- a/examples/hot_reload/graph.v +++ b/examples/hot_reload/graph.v @@ -4,7 +4,8 @@ import gx import gg import time import glfw -// import math +import math +import os const ( Size = 700 @@ -16,6 +17,7 @@ struct Context { } fn main() { + os.clear() glfw.init() ctx:= &Context{ gg: gg.new_context(gg.Cfg { @@ -40,11 +42,12 @@ fn main() { fn (ctx &Context) draw() { ctx.gg.draw_line(0, Size / 2, Size, Size / 2) // x axis ctx.gg.draw_line(Size / 2, 0, Size / 2, Size) // y axis - center := f64(Size / 2) + center := f64(Size / 2) + mut y := 0.0 for x := -10.0; x <= 10.0; x += 0.002 { - y := x * x - 1 - //y := (x + 3) * (x + 3) - 1 - //y := math.sqrt(30.0 - x * x) + y = x * x - 1 + //y = (x + 3) * (x + 3) - 1 + //y = math.sqrt(30.0 - x * x) ctx.gg.draw_rect(center + x * Scale, center - y * Scale, 1, 1, gx.Black) //ctx.gg.draw_rect(center + x * Scale, center + y * Scale, 1, 1, gx.Black) } diff --git a/examples/hot_reload/message.v b/examples/hot_reload/message.v index 0c88cc65e1..63b09b31bb 100644 --- a/examples/hot_reload/message.v +++ b/examples/hot_reload/message.v @@ -2,6 +2,7 @@ // v -live message.v module main +import os import time [live] @@ -10,6 +11,7 @@ fn print_message() { } fn main() { + os.clear() for { print_message() time.sleep_ms(500)