compiler: streamline main function handling

* compiler: streamline C main function generation

* fix most tests

* compiler: fix for 'go update()' in graph.v . More precise parser error messages.

* Fix temporarily examples/hot_reload/message.v by using os inside it (os.clear).

* Make graph.v easier to quickly modify by defining y outside the loop.

* Fix failure of /v/nv/compiler/tests/defer_test.v when run with 'v -g' (#line directive was not on its own line, but right after } ).

* Do not pass the os.args to tests, even if the tests import os (they are more stable when run in a controlled environment).

* fix declared and not used in the js backend.

* fix js main => main__main too.
pull/2159/head
Delyan Angelov 2019-09-28 20:42:29 +03:00 committed by Alexander Medvednikov
parent 0160c7a89d
commit a4cbe78d97
10 changed files with 141 additions and 92 deletions

View File

@ -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

View File

@ -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')

View File

@ -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 {

View File

@ -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){

View File

@ -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

View File

@ -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, '', '')
}

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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)