diff --git a/compiler/cgen.v b/compiler/cgen.v index 689d01843a..740924e6e1 100644 --- a/compiler/cgen.v +++ b/compiler/cgen.v @@ -20,11 +20,11 @@ struct CGen { fns []string so_fns []string consts_init []string - lines []string //buf strings.Builder is_user bool mut: - run Pass + lines []string + pass Pass nogen bool tmp_line string cur_line string @@ -53,7 +53,7 @@ fn new_cgen(out_name_c string) *CGen { } fn (g mut CGen) genln(s string) { - if g.nogen || g.run != .main { + if g.nogen || g.pass != .main { return } if g.is_tmp { @@ -72,7 +72,7 @@ fn (g mut CGen) genln(s string) { } fn (g mut CGen) gen(s string) { - if g.nogen || g.run != .main { + if g.nogen || g.pass != .main { return } if g.is_tmp { @@ -84,7 +84,7 @@ fn (g mut CGen) gen(s string) { } fn (g mut CGen) resetln(s string) { - if g.nogen || g.run != .main { + if g.nogen || g.pass != .main { return } if g.is_tmp { @@ -127,7 +127,7 @@ fn (g mut CGen) add_placeholder() int { } fn (g mut CGen) set_placeholder(pos int, val string) { - if g.nogen || g.run != .main { + if g.nogen || g.pass != .main { return } // g.lines.set(pos, val) @@ -153,7 +153,7 @@ fn (g mut CGen) add_placeholder2() int { } fn (g mut CGen) set_placeholder2(pos int, val string) { - if g.nogen || g.run != .main { + if g.nogen || g.pass != .main { return } if g.is_tmp { @@ -219,21 +219,21 @@ fn (p mut Parser) print_prof_counters() string { } fn (p mut Parser) gen_type(s string) { - if !p.first_run() { + if !p.first_pass() { return } p.cgen.types << s } fn (p mut Parser) gen_typedef(s string) { - if !p.first_run() { + if !p.first_pass() { return } p.cgen.typedefs << s } fn (p mut Parser) gen_type_alias(s string) { - if !p.first_run() { + if !p.first_pass() { return } p.cgen.type_aliases << s diff --git a/compiler/comptime.v b/compiler/comptime.v new file mode 100644 index 0000000000..26244bb6d7 --- /dev/null +++ b/compiler/comptime.v @@ -0,0 +1,203 @@ +// 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 ( + vweb.tmpl // for `$vweb_html()` + os +) + +fn (p mut Parser) comp_time() { + p.check(.dollar) + if p.tok == .key_if { + p.check(.key_if) + p.fspace() + not := p.tok == .not + if not { + p.check(.not) + } + name := p.check_name() + p.fspace() + if name in SupportedPlatforms { + ifdef_name := os_name_to_ifdef(name) + if not { + p.genln('#ifndef $ifdef_name') + } + else { + p.genln('#ifdef $ifdef_name') + } + p.check(.lcbr) + p.statements_no_rcbr() + if ! (p.tok == .dollar && p.peek() == .key_else) { + p.genln('#endif') + } + } + else { + println('Supported platforms:') + println(SupportedPlatforms) + p.error('unknown platform `$name`') + } + } + else if p.tok == .key_for { + p.next() + name := p.check_name() + if name != 'field' { + p.error('for field only') + } + p.check(.key_in) + p.check_name() + p.check(.dot) + p.check_name()// fields + p.check(.lcbr) + // for p.tok != .rcbr && p.tok != .eof { + res_name := p.check_name() + println(res_name) + p.check(.dot) + p.check(.dollar) + p.check(.name) + p.check(.assign) + p.cgen.start_tmp() + p.bool_expression() + val := p.cgen.end_tmp() + println(val) + p.check(.rcbr) + // } + } + else if p.tok == .key_else { + p.next() + p.check(.lcbr) + p.genln('#else') + p.statements_no_rcbr() + p.genln('#endif') + } + // $vweb.html() + // Compile vweb html template to V code, parse that V code and embed the resulting V functions + // that returns an html string + else if p.tok == .name && p.lit == 'vweb' { + path := p.cur_fn.name + '.html' + if !os.file_exists(path) { + p.error('vweb HTML template "$path" not found') + } + p.check(.name) // skip `vweb.html()` TODO + p.check(.dot) + p.check(.name) + p.check(.lpar) + p.check(.rpar) + v_code := tmpl.compile_template(path) + os.write_file('.vwebtmpl.v', v_code.clone()) // TODO don't need clone, compiler bug + p.genln('') + // Parse the function and embed resulting C code in current function so that + // all variables are available. + pos := p.cgen.lines.len - 1 + mut pp := p.v.new_parser('.vwebtmpl.v', Pass.main) + os.rm('.vwebtmpl.v') + pp.is_vweb = true + pp.cur_fn = p.cur_fn // give access too all variables in current function + pp.parse() + tmpl_fn_body := p.cgen.lines.slice(pos + 2, p.cgen.lines.len).join('\n').clone() + end_pos := tmpl_fn_body.last_index('Builder_str( sb )') + 19 // TODO + p.cgen.lines = p.cgen.lines.left(pos) + p.genln('/////////////////// tmpl start') + p.genln(tmpl_fn_body.left(end_pos)) + p.genln('/////////////////// tmpl end') + // `app.vweb.html(index_view())` + receiver := p.cur_fn.args[0] + dot := if receiver.is_mut { '->' } else { '.' } + p.genln('vweb__Context_html($receiver.name $dot vweb, tmpl_res)') + } + else { + p.error('bad comptime expr') + } +} + +// #include, #flag, #v +fn (p mut Parser) chash() { + hash := p.lit.trim_space() + // println('chsh() file=$p.file is_sig=${p.is_sig()} hash="$hash"') + p.next() + is_sig := p.is_sig() + if hash.starts_with('flag ') { + mut flag := hash.right(5) + // No the right os? Skip! + // mut ok := true + if hash.contains('linux') && p.os != .linux { + return + } + else if hash.contains('darwin') && p.os != .mac { + return + } + else if hash.contains('windows') && (p.os != .windows && p.os != .msvc) { + return + } + // Remove "linux" etc from flag + if flag.contains('linux') || flag.contains('darwin') || flag.contains('windows') { + pos := flag.index(' ') + flag = flag.right(pos) + } + has_vroot := flag.contains('@VROOT') + flag = flag.trim_space().replace('@VROOT', p.vroot) + if p.table.flags.contains(flag) { + return + } + p.log('adding flag "$flag"') + // `@VROOT/thirdparty/glad/glad.o`, make sure it exists, otherwise build it + if has_vroot && flag.contains('.o') { + if p.os == .msvc { + build_thirdparty_obj_file_with_msvc(flag) + } + else { + build_thirdparty_obj_file(flag) + } + } + p.table.flags << flag + return + } + if hash.starts_with('include') { + if p.first_pass() && !is_sig { + p.cgen.includes << '#$hash' + return + } + } + // TODO remove after ui_mac.m is removed + else if hash.contains('embed') { + pos := hash.index('embed') + 5 + file := hash.right(pos) + if p.pref.build_mode != BuildMode.default_mode { + p.genln('#include $file') + } + } + else if hash.contains('define') { + // Move defines on top + p.cgen.includes << '#$hash' + } + else if hash == 'v' { + println('v script') + //p.v_script = true + } + else { + if !p.can_chash { + p.error('bad token `#` (embedding C code is no longer supported)') + } + p.genln(hash) + } +} + +// `user.$method()` (`method` is a string) +fn (p mut Parser) comptime_method_call(typ Type) { + p.cgen.cur_line = '' + p.check(.dollar) + var := p.check_name() + for method in typ.methods { + if method.typ != 'void' { + continue + } + receiver := method.args[0] + amp := if receiver.is_mut { '&' } else { '' } + p.gen('if ( string_eq($var, _STR("$method.name")) ) ${typ.name}_$method.name($amp $p.expr_var.name);') + } + p.check(.lpar) + p.check(.rpar) +} + diff --git a/compiler/fn.v b/compiler/fn.v index edc8b0d719..21fa003cb4 100644 --- a/compiler/fn.v +++ b/compiler/fn.v @@ -30,6 +30,7 @@ mut: returns_error bool is_decl bool // type myfn fn(int, int) defer_text string + //gen_types []string } fn (f &Fn) find_var(name string) Var { @@ -110,7 +111,7 @@ fn (p mut Parser) fn_decl() { 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 { + 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 { @@ -136,7 +137,7 @@ fn (p mut Parser) fn_decl() { 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 { + if !p.first_pass() && !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`') @@ -203,7 +204,7 @@ fn (p mut Parser) fn_decl() { 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 { + if p.first_pass() && 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 { @@ -213,13 +214,19 @@ fn (p mut Parser) fn_decl() { // Generic? mut is_generic := false if p.tok == .lt { + is_generic = true 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 + if p.first_pass() { + p.table.register_generic_fn(f.name) + } else { + //gen_types := p.table.fn_gen_types(f.name) + //println(gen_types) + } } // Args (...) p.fn_args(mut f) @@ -247,14 +254,13 @@ fn (p mut Parser) fn_decl() { p.fgen(' ') p.check(.lcbr) } - // Register option ? type + // 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' { @@ -263,21 +269,19 @@ fn (p mut Parser) fn_decl() { 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() + dll_export_linkage := if p.os == .msvc && p.attr == 'live' && p.pref.is_so { + '__declspec(dllexport) ' + } else { + '' + } + // if p.file_name != '.vwebtmpl.v' { + // oif !p.name.ends_with('_vwebview') { + if !p.is_vweb { +//println('SETTING CUR FN TO "$f.name" "$p.file_name"') + p.cur_fn = f + } + // 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 @@ -285,7 +289,27 @@ fn (p mut Parser) fn_decl() { if p.pref.obfuscate { p.genln('; // $f.name') } - p.genln('$dll_export_linkage$typ $fn_name_cgen($str_args) {') + // Generate this function's body for all generic types + if is_generic { + gen_types := p.table.fn_gen_types(f.name) + // Remember current scanner position, go back here for each type + // TODO remove this once tokens are cached in `new_parser()` + cur_pos := p.scanner.pos + cur_tok := p.tok + cur_lit := p.lit + for gen_type in gen_types { + p.genln('$dll_export_linkage$typ ${fn_name_cgen}_$gen_type($str_args) {') + p.genln('// T start $p.pass ${p.strtok()}') + p.cur_gen_type = gen_type // TODO support more than T + p.statements() + p.scanner.pos = cur_pos + p.tok = cur_tok + p.lit = cur_lit + } + } + else { + p.genln('$dll_export_linkage$typ $fn_name_cgen($str_args) {') + } } if is_fn_header { p.genln('$typ $fn_name_cgen($str_args);') @@ -294,13 +318,12 @@ fn (p mut Parser) fn_decl() { 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 == '' { + if p.first_pass() && receiver_t.name == '' { // println('fn decl !!!!!!! REG PH $receiver_typ') p.table.register_type2(Type { name: receiver_typ.replace('*', '') @@ -315,8 +338,9 @@ fn (p mut Parser) fn_decl() { // 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 || p.first_pass() || is_live || is_fn_header || skip_main_in_test { + // First pass? Skip the body for now + // Look for generic calls. if !is_sig && !is_fn_header { mut opened_scopes := 0 mut closed_scopes := 0 @@ -328,10 +352,24 @@ fn (p mut Parser) fn_decl() { closed_scopes++ } p.next() + // find `foo()` in function bodies and register generic types + // TODO remove this once tokens are cached + if p.tok == .gt && p.prev_tok == .name && p.prev_tok2 == .lt && + p.scanner.text[p.scanner.pos-1] != `T` { + p.scanner.pos -= 3 + for p.scanner.pos > 0 && is_name_char(p.scanner.text[p.scanner.pos]) || p.scanner.text[p.scanner.pos] == `.` || + p.scanner.text[p.scanner.pos] == `<` { + p.scanner.pos-- + } + p.scanner.pos-- + p.next() + // Run the function in the firt pass to register the generic type + p.name_expr() + } 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? + // 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 @@ -340,18 +378,18 @@ fn (p mut Parser) fn_decl() { } } // Live code reloading? Load all fns from .so - if is_live && p.first_run() && p.mod == 'main' { + 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 )' } - // Actual fn declaration! + // 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}' + fn_decl += '; // $f.name' } // Add function definition to the top - if !is_c && f.name != 'main' && p.first_run() { + if !is_c && f.name != 'main' && p.first_pass() { // TODO hack to make Volt compile without -embed_vlib if f.name == 'darwin__nsstring' && p.pref.build_mode == .default_mode { return @@ -360,12 +398,10 @@ fn (p mut Parser) 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') { @@ -404,13 +440,18 @@ _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0); // 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). + // 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' { 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;' } + if is_generic { + // Don't need to generate body for the actual generic definition + p.cgen.nogen = true + } 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()) @@ -420,12 +461,10 @@ _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0); 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) @@ -436,12 +475,16 @@ _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0); // 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('}') + if !is_generic { + p.genln('}') + } return } p.check_unused_variables() p.cur_fn = EmptyFn - p.genln('}') + if !is_generic { + p.genln('}') + } } fn (p mut Parser) check_unused_variables() { @@ -539,6 +582,9 @@ fn (p mut Parser) async_fn_call(f Fn, method_ph int, receiver_var, receiver_type } fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type string) { +if p.fileis('vtalk2.v') { +//println('FN CALL k $f.name') +} 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') } @@ -550,7 +596,25 @@ fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type strin p.error('use `malloc()` instead of `C.malloc()`') } } - cgen_name := p.table.cgen_name(f) + mut cgen_name := p.table.cgen_name(f) + p.next() + mut gen_type := '' + if p.tok == .lt { + p.check(.lt) + gen_type = p.check_name() +// `foo()` +// If we are in the first pass, we need to add `Bar` type to the generic function `foo`, +// so that generic `foo`s body can be generated for each type in the second pass. +if p.first_pass() { +println('reging $gen_type in $f.name') +p.table.register_generic_fn_type(f.name, gen_type) +// Function bodies are skipped in the first passed, we only need to register the generic type here. +return +} + cgen_name += '_' + gen_type + + p.check(.gt) + } // if p.pref.is_prof { // p.cur_fn.called_fns << cgen_name // } @@ -590,8 +654,8 @@ fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type strin } p.cgen.set_placeholder(method_ph, '$cast $method_call') } - p.next() - p.fn_call_args(f) + // foo() + p.fn_call_args(mut f) p.gen(')') p.calling_c = false // println('end of fn call typ=$f.typ') @@ -648,7 +712,7 @@ fn (p mut Parser) fn_args(f mut Fn) { '\nreturn values instead: `foo(n mut int)` => `foo(n int) int`') } for name in names { - if !p.first_run() && !p.table.known_type(typ) { + if !p.first_pass() && !p.table.known_type(typ) { p.error('fn_args: unknown type $typ') } if is_mut { @@ -678,7 +742,7 @@ fn (p mut Parser) fn_args(f mut Fn) { } // foo *(1, 2, 3, mut bar)* -fn (p mut Parser) fn_call_args(f *Fn) *Fn { +fn (p mut Parser) fn_call_args(f mut Fn) *Fn { // p.gen('(') // println('fn_call_args() name=$f.name args.len=$f.args.len') // C func. # of args is not known @@ -762,10 +826,10 @@ fn (p mut Parser) fn_call_args(f *Fn) *Fn { p.error(error_msg) } p.cgen.resetln(p.cgen.cur_line.left(index)) - p.create_type_string(T, name) + p.scanner.create_type_string(T, name) p.cgen.cur_line.replace(typ, '') p.next() - return p.fn_call_args(f) + return p.fn_call_args(mut f) } p.error(error_msg) } diff --git a/compiler/jsgen.v b/compiler/jsgen.v index 987a4237e2..55b5758101 100644 --- a/compiler/jsgen.v +++ b/compiler/jsgen.v @@ -22,7 +22,7 @@ fn (p mut Parser) gen_json_for_type(typ Type) { if t == 'int' || t == 'string' || t == 'bool' { return } - if p.first_run() { + if p.first_pass() { return } // println('gen_json_for_type( $typ.name )') diff --git a/compiler/main.v b/compiler/main.v index 0fc5ff9e94..a28337130f 100644 --- a/compiler/main.v +++ b/compiler/main.v @@ -179,7 +179,7 @@ fn (v mut V) compile() { p.parse() } // Main pass - cgen.run = Pass.main + cgen.pass = Pass.main if v.pref.is_play { cgen.genln('#define VPLAY (1) ') } diff --git a/compiler/parser.v b/compiler/parser.v index 0da6048e91..da469f4abf 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -4,9 +4,11 @@ module main -import os -import rand -import strings +import ( + os + rand + strings +) struct Var { mut: @@ -35,6 +37,7 @@ struct Parser { file_path string // "/home/user/hello.v" file_name string // "hello.v" mut: + v *V scanner *Scanner // tokens []Token // TODO cache all tokens, right now they have to be scanned twice token_idx int @@ -45,7 +48,7 @@ mut: cgen *CGen table *Table import_table *FileImportTable // Holds imports for just the file being parsed - run Pass // TODO rename `run` to `pass` + pass Pass os OS mod string inside_const bool @@ -73,6 +76,8 @@ mut: var_decl_name string // To allow declaring the variable so that it can be used in the struct initialization building_v bool is_alloc bool // Whether current expression resulted in an allocation + cur_gen_type string // "App" to replace "T" in current generic function + is_vweb bool } const ( @@ -84,29 +89,30 @@ const ( MaxModuleDepth = 4 ) -fn (c mut V) new_parser(path string, run Pass) Parser { - c.log('new_parser("$path")') - c.cgen.run = run +fn (v mut V) new_parser(path string, pass Pass) Parser { + v.log('new_parser("$path")') + v.cgen.pass = pass mut p := Parser { + v: v file_path: path file_name: path.all_after('/') scanner: new_scanner(path) - table: c.table + table: v.table import_table: new_file_import_table(path) cur_fn: EmptyFn - cgen: c.cgen - is_script: (c.pref.is_script && path == c.dir) - pref: c.pref - os: c.os - run: run - vroot: c.vroot - building_v: !c.pref.is_repl && (path.contains('compiler/') || + cgen: v.cgen + is_script: (v.pref.is_script && path == v.dir) + pref: v.pref + os: v.os + pass: pass + vroot: v.vroot + building_v: !v.pref.is_repl && (path.contains('compiler/') || path.contains('v/vlib')) } - c.cgen.line_directives = c.pref.is_debuggable - c.cgen.file = path + v.cgen.line_directives = v.pref.is_debuggable + v.cgen.file = path p.next() // p.scanner.debug_tokens() @@ -132,7 +138,7 @@ fn (p &Parser) log(s string) { } fn (p mut Parser) parse() { - p.log('\nparse() run=$p.run file=$p.file_name tok=${p.strtok()}')// , "script_file=", script_file) + p.log('\nparse() run=$p.pass file=$p.file_name tok=${p.strtok()}')// , "script_file=", script_file) // `module main` is not required if it's a single file program if p.is_script || p.pref.is_test { p.mod = 'main' @@ -158,7 +164,7 @@ fn (p mut Parser) parse() { p.table.register_package(fq_mod) // replace "." with "_dot_" in module name for C variable names p.mod = fq_mod.replace('.', '_dot_') - if p.run == .imports { + if p.pass == .imports { for p.tok == .key_import && p.peek() != .key_const { p.imports() } @@ -256,7 +262,7 @@ fn (p mut Parser) parse() { p.cur_fn = MainFn p.check_unused_variables() } - if false && !p.first_run() && p.fileis('main.v') { + if false && !p.first_pass() && p.fileis('main.v') { out := os.create('/var/tmp/fmt.v') or { panic('failed to create fmt.v') return @@ -270,7 +276,7 @@ fn (p mut Parser) parse() { if p.is_script && !p.pref.is_test { // cur_fn is empty since there was no fn main declared // we need to set it to save and find variables - if p.first_run() { + if p.first_pass() { if p.cur_fn.name == '' { p.cur_fn = MainFn } @@ -320,43 +326,6 @@ fn (p mut Parser) imports() { p.register_import() } -fn (p mut Parser) register_import() { - if p.tok != .name { - p.error('bad import format') - } - mut pkg := p.lit.trim_space() - mut mod_alias := pkg - // submodule support - mut depth := 1 - p.next() - for p.tok == .dot { - p.check(.dot) - submodule := p.check_name() - mod_alias = submodule - pkg += '.' + submodule - depth++ - if depth > MaxModuleDepth { - p.error('module depth of $MaxModuleDepth exceeded: $pkg') - } - } - // aliasing (import encoding.base64 as b64) - if p.tok == .key_as && p.peek() == .name { - p.check(.key_as) - mod_alias = p.check_name() - } - // add import to file scope import table - p.import_table.register_alias(mod_alias, pkg) - // Make sure there are no duplicate imports - if p.table.imports.contains(pkg) { - return - } - p.log('adding import $pkg') - p.table.imports << pkg - p.table.register_package(pkg) - - p.fgenln(' ' + pkg) -} - fn (p mut Parser) const_decl() { is_import := p.tok == .key_import p.inside_const = true @@ -371,9 +340,9 @@ fn (p mut Parser) const_decl() { for p.tok == .name { // `Age = 20` mut name := p.check_name() - if p.pref.is_play && ! (name[0] >= `A` && name[0] <= `Z`) { - p.error('const name must be capitalized') - } + //if ! (name[0] >= `A` && name[0] <= `Z`) { + //p.error('const name must be capitalized') + //} // Imported consts (like GL_TRIANG.leS) dont need pkg prepended (gl__GL_TRIANG.leS) if !is_import { name = p.prepend_pkg(name) @@ -383,11 +352,11 @@ fn (p mut Parser) const_decl() { p.check_space(.assign) typ = p.expression() } - if p.first_run() && !is_import && p.table.known_const(name) { + if p.first_pass() && !is_import && p.table.known_const(name) { p.error('redefinition of `$name`') } p.table.register_const(name, typ, p.mod, is_import) - if p.run == .main && !is_import { + if p.pass == .main && !is_import { // TODO hack // cur_line has const's value right now. if it's just a number, then optimize generation: // output a #define so that we don't pollute the binary with unnecessary global vars @@ -437,7 +406,7 @@ fn (p mut Parser) interface_method(field_name, receiver string) &Fn { is_method: true receiver_typ: receiver } - p.log('is interface. field=$field_name run=$p.run') + p.log('is interface. field=$field_name run=$p.pass') p.fn_args(mut method) if p.scanner.has_gone_over_line_end() { method.typ = 'void' @@ -488,7 +457,7 @@ fn (p mut Parser) struct_decl() { if !is_c && !p.builtin_pkg && p.mod != 'main' { name = p.prepend_pkg(name) } - if p.run == .decl && p.table.known_type(name) { + if p.pass == .decl && p.table.known_type(name) { p.error('`$name` redeclared') } // Generate type definitions @@ -543,7 +512,7 @@ fn (p mut Parser) struct_decl() { fmt_max_len = field.name.len } } - println('fmt max len = $max_len nrfields=$typ.fields.len pass=$p.run') + println('fmt max len = $max_len nrfields=$typ.fields.len pass=$p.pass') */ mut did_gen_something := false for p.tok != .rcbr { @@ -616,7 +585,7 @@ fn (p mut Parser) struct_decl() { p.fgenln('') } - if !is_ph && p.first_run() { + if !is_ph && p.first_pass() { p.table.register_type2(typ) //println('registering 1 nrfields=$typ.fields.len') } @@ -654,7 +623,7 @@ fn (p mut Parser) enum_decl(_enum_name string) { fields << field p.fgenln('') name := '${p.mod}__${enum_name}_$field' - if p.run == .main { + if p.pass == .main { p.cgen.consts << '#define $name $val' } if p.tok == .comma { @@ -738,19 +707,14 @@ if p.scanner.line_comment != '' { fn (p mut Parser) error(s string) { // Dump all vars and types for debugging - if false { - //file_types := os.create('$TmpPath/types') - //file_vars := os.create('$TmpPath/vars') - // ////debug("ALL T", q.J(p.table.types)) + if p.pref.is_debug { // os.write_to_file('/var/tmp/lang.types', '')//pes(p.table.types)) - // //debug("ALL V", q.J(p.table.vars)) // os.write_to_file('/var/tmp/lang.vars', q.J(p.table.vars)) - //file_types.close() - //file_vars.close() - } - if p.pref.is_verbose { - println('pass=$p.run fn=`$p.cur_fn.name`') + os.write_file('fns.txt', p.table.debug_fns()) } + //if p.pref.is_verbose { + println('pass=$p.pass fn=`$p.cur_fn.name`') + //} p.cgen.save() // V git pull hint cur_path := os.getwd() @@ -769,8 +733,8 @@ fn (p mut Parser) error(s string) { p.scanner.error(s.replace('array_', '[]').replace('__', '.').replace('Option_', '?')) } -fn (p &Parser) first_run() bool { - return p.run == .decl +fn (p &Parser) first_pass() bool { + return p.pass == .decl } // TODO return Type instead of string? @@ -890,13 +854,13 @@ fn (p mut Parser) get_type() string { mut t := p.table.find_type(typ) // "typ" not found? try "pkg__typ" if t.name == '' && !p.builtin_pkg { - // && !p.first_run() { + // && !p.first_pass() { if !typ.contains('array_') && p.mod != 'main' && !typ.contains('__') && !typ.starts_with('[') { typ = p.prepend_pkg(typ) } t = p.table.find_type(typ) - if t.name == '' && !p.pref.translated && !p.first_run() && !typ.starts_with('[') { + if t.name == '' && !p.pref.translated && !p.first_pass() && !typ.starts_with('[') { println('get_type() bad type') // println('all registered types:') // for q in p.table.types { @@ -919,7 +883,7 @@ fn (p mut Parser) get_type() string { } else if is_arr { typ = 'array_$typ' - // p.log('ARR TYPE="$typ" run=$p.run') + // p.log('ARR TYPE="$typ" run=$p.pass') // We come across "[]User" etc ? p.register_array(typ) } @@ -1050,14 +1014,6 @@ fn (p mut Parser) vh_genln(s string) { p.vh_lines << s } -fn (p mut Parser) fmt_inc() { - p.scanner.fmt_indent++ -} - -fn (p mut Parser) fmt_dec() { - p.scanner.fmt_indent-- -} - fn (p mut Parser) statement(add_semi bool) string { p.cgen.is_tmp = false tok := p.tok @@ -1342,13 +1298,17 @@ fn (p mut Parser) bterm() string { // also called on *, &, @, . (enum) fn (p mut Parser) name_expr() string { - p.log('\nname expr() pass=$p.run tok=${p.tok.str()} $p.lit') +if p.fileis('vtalk') { + //println('\nname expr() pass=$p.pass tok=${p.tok.str()} lit="$p.lit" ${p.scanner.line_nr+1}') +} // print('known type:') // println(p.table.known_type(p.lit)) // hack for struct_init TODO +/* hack_pos := p.scanner.pos hack_tok := p.tok hack_lit := p.lit +*/ ph := p.cgen.add_placeholder() // amp ptr := p.tok == .amp @@ -1379,7 +1339,7 @@ fn (p mut Parser) name_expr() string { } // enum value? (`color == .green`) if p.tok == .dot { - //println('got enum dot val $p.left_type pass=$p.run $p.scanner.line_nr left=$p.left_type') + //println('got enum dot val $p.left_type pass=$p.pass $p.scanner.line_nr left=$p.left_type') T := p.find_type(p.expected_type) if T.is_enum { p.check(.dot) @@ -1445,7 +1405,7 @@ fn (p mut Parser) name_expr() string { } return typ } - // if known_type || is_c_struct_init || (p.first_run() && p.peek() == .lcbr) { + // if known_type || is_c_struct_init || (p.first_pass() && p.peek() == .lcbr) { // known type? int(4.5) or Color.green (enum) if p.table.known_type(name) { // float(5), byte(0), (*int)(ptr) etc @@ -1480,16 +1440,24 @@ fn (p mut Parser) name_expr() string { } else if p.peek() == .lcbr { // go back to name start (pkg.name) +/* p.scanner.pos = hack_pos p.tok = hack_tok p.lit = hack_lit +*/ // TODO hack. If it's a C type, we may need to add struct before declaration: // a := &C.A{} ==> struct A* a = malloc(sizeof(struct A)); if is_c_struct_init { p.is_c_struct_init = true p.cgen.insert_before('struct /*c struct init*/') } - return p.struct_init(is_c_struct_init) + if ptr { + name += '*' // `&User{}` => type `User*` + } + if name == 'T' { + name = p.cur_gen_type + } + return p.struct_init(name, is_c_struct_init) } } // C fn @@ -1529,7 +1497,7 @@ fn (p mut Parser) name_expr() string { mut f := p.table.find_fn(name) if f.name == '' { // We are in a second pass, that means this function was not defined, throw an error. - if !p.first_run() { + if !p.first_pass() { // V script? Try os module. if p.v_script { name = name.replace('main__', 'os__') @@ -1554,7 +1522,8 @@ fn (p mut Parser) name_expr() string { } // no () after func, so func is an argument, just gen its name // TODO verify this and handle errors - if p.peek() != .lpar { + peek := p.peek() + if peek != .lpar && peek != .lt { p.gen(p.table.cgen_name(f)) p.next() return 'void*' @@ -1564,6 +1533,9 @@ fn (p mut Parser) name_expr() string { // p.error('`$f.name` used as value') } p.log('calling function') +if p.fileis('vtalk') { +//println('calling fn $f.name') +} p.fn_call(f, 0, '', '') // dot after a function call: `get_user().age` if p.tok == .dot { @@ -1593,7 +1565,7 @@ fn (p mut Parser) var_expr(v Var) string { //p.print_tok() T := p.table.find_type(typ) p.gen('(') - p.fn_call_args(T.func) + p.fn_call_args(mut T.func) p.gen(')') typ = T.func.typ } @@ -1601,7 +1573,6 @@ fn (p mut Parser) var_expr(v Var) string { // users[0].name if p.tok == .lsbr { typ = p.index_expr(typ, fn_ph) - // ////println('QQQQ KEK $typ') } // a.b.c().d chain // mut dc := 0 @@ -1651,6 +1622,7 @@ fn (p mut Parser) var_expr(v Var) string { return typ } +// for debugging only fn (p &Parser) fileis(s string) bool { return p.scanner.file_path.contains(s) } @@ -1659,19 +1631,23 @@ fn (p &Parser) fileis(s string) bool { // user.company.name => `str_typ` is `Company` fn (p mut Parser) dot(str_typ string, method_ph int) string { p.check(.dot) + typ := p.find_type(str_typ) + if typ.name.len == 0 { + p.error('dot(): cannot find type `$str_typ`') + } + if p.tok == .dollar { + p.comptime_method_call(typ) + return 'void' + } field_name := p.lit p.fgen(field_name) p.log('dot() field_name=$field_name typ=$str_typ') //if p.fileis('main.v') { //println('dot() field_name=$field_name typ=$str_typ prev_tok=${prev_tok.str()}') //} - typ := p.find_type(str_typ) - if typ.name.len == 0 { - p.error('dot(): cannot find type `$str_typ`') - } has_field := p.table.type_has_field(typ, field_name) has_method := p.table.type_has_method(typ, field_name) - if !typ.is_c && !has_field && !has_method && !p.first_run() { + if !typ.is_c && !has_field && !has_method && !p.first_pass() { if typ.name.starts_with('Option_') { opt_type := typ.name.right(7) p.error('unhandled option type: $opt_type?') @@ -1944,7 +1920,7 @@ fn (p mut Parser) index_expr(typ string, fn_ph int) string { // returns resulting type fn (p mut Parser) expression() string { if p.scanner.file_path.contains('test_test') { - println('expression() pass=$p.run tok=') + println('expression() pass=$p.pass tok=') p.print_tok() } p.cgen('/* expr start*/') @@ -2298,32 +2274,6 @@ fn (p mut Parser) char_expr() { } -fn (p mut Parser) typ_to_fmt(typ string, level int) string { - t := p.table.find_type(typ) - if t.is_enum { - return '%d' - } - switch typ { - case 'string': return '%.*s' - case 'ustring': return '%.*s' - case 'byte', 'int', 'char', 'byte', 'bool', 'u32', 'i32', 'i16', 'u16', 'i8', 'u8': return '%d' - case 'f64', 'f32': return '%f' - case 'i64', 'u64': return '%lld' - case 'byte*', 'byteptr': return '%s' - // case 'array_string': return '%s' - // case 'array_int': return '%s' - case 'void': p.error('cannot interpolate this value') - default: - if typ.ends_with('*') { - return '%p' - } - } - if t.parent != '' && level == 0 { - return p.typ_to_fmt(t.parent, level+1) - } - return '' -} - fn format_str(str string) string { str = str.replace('"', '\\"') $if windows { @@ -2546,7 +2496,7 @@ fn (p mut Parser) array_init() string { if is_fixed_size { p.next() p.gen(' }') - if !p.first_run() { + if !p.first_pass() { // If we are defining a const array, we don't need to specify the type: // `a = {1,2,3}`, not `a = (int[]) {1,2,3}` if p.inside_const { @@ -2567,8 +2517,8 @@ fn (p mut Parser) array_init() string { } p.gen(' })') // p.gen('$new_arr($vals.len, $vals.len, sizeof($typ), ($typ[]) $c_arr );') - // TODO why need !first_run()?? Otherwise it goes to the very top of the out.c file - if !p.first_run() { + // TODO why need !first_pass()?? Otherwise it goes to the very top of the out.c file + if !p.first_pass() { if i == 0 { p.cgen.set_placeholder(new_arr_ph, '$new_arr($i, $i, sizeof($typ), ($typ[]) {EMPTY_STRUCT_INIT ') } else { @@ -2580,32 +2530,9 @@ fn (p mut Parser) array_init() string { return typ } -fn (p mut Parser) register_array(typ string) { - if typ.contains('*') { - println('bad arr $typ') - return - } - if !p.table.known_type(typ) { - p.register_type_with_parent(typ, 'array') - p.cgen.typedefs << 'typedef array $typ;' - } -} - - -fn (p mut Parser) register_map(typ string) { - if typ.contains('*') { - println('bad map $typ') - return - } - if !p.table.known_type(typ) { - p.register_type_with_parent(typ, 'map') - p.cgen.typedefs << 'typedef map $typ;' - } -} - -fn (p mut Parser) struct_init(is_c_struct_init bool) string { +fn (p mut Parser) struct_init(typ string, is_c_struct_init bool) string { p.is_struct_init = true - mut typ := p.get_type() + p.next() p.scanner.fmt_out.cut(typ.len) ptr := typ.contains('*') // TODO tm struct struct bug @@ -2810,153 +2737,6 @@ fn os_name_to_ifdef(name string) string { return '' } -fn (p mut Parser) comp_time() { - p.check(.dollar) - if p.tok == .key_if { - p.check(.key_if) - p.fspace() - not := p.tok == .not - if not { - p.check(.not) - } - name := p.check_name() - p.fspace() - if name in SupportedPlatforms { - ifdef_name := os_name_to_ifdef(name) - if not { - p.genln('#ifndef $ifdef_name') - } - else { - p.genln('#ifdef $ifdef_name') - } - p.check(.lcbr) - p.statements_no_rcbr() - if ! (p.tok == .dollar && p.peek() == .key_else) { - p.genln('#endif') - } - } - else { - println('Supported platforms:') - println(SupportedPlatforms) - p.error('unknown platform `$name`') - } - } - else if p.tok == .key_for { - p.next() - name := p.check_name() - if name != 'field' { - p.error('for field only') - } - p.check(.key_in) - p.check_name() - p.check(.dot) - p.check_name()// fields - p.check(.lcbr) - // for p.tok != .rcbr && p.tok != .eof { - res_name := p.check_name() - println(res_name) - p.check(.dot) - p.check(.dollar) - p.check(.name) - p.check(.assign) - p.cgen.start_tmp() - p.bool_expression() - val := p.cgen.end_tmp() - println(val) - p.check(.rcbr) - // } - } - else if p.tok == .key_else { - p.next() - p.check(.lcbr) - p.genln('#else') - p.statements_no_rcbr() - p.genln('#endif') - } - else { - p.error('bad comptime expr') - } -} - -fn (p mut Parser) chash() { - hash := p.lit.trim_space() - // println('chsh() file=$p.file is_sig=${p.is_sig()} hash="$hash"') - p.next() - is_sig := p.is_sig() - if is_sig { - // p.cgen.nogen = true - } - if hash.starts_with('flag ') { - mut flag := hash.right(5) - // No the right os? Skip! - // mut ok := true - if hash.contains('linux') && p.os != .linux { - return - } - else if hash.contains('darwin') && p.os != .mac { - return - } - else if hash.contains('windows') && (p.os != .windows && p.os != .msvc) { - return - } - // Remove "linux" etc from flag - if flag.contains('linux') || flag.contains('darwin') || flag.contains('windows') { - pos := flag.index(' ') - flag = flag.right(pos) - } - has_vroot := flag.contains('@VROOT') - flag = flag.trim_space().replace('@VROOT', p.vroot) - if p.table.flags.contains(flag) { - return - } - p.log('adding flag "$flag"') - // `@VROOT/thirdparty/glad/glad.o`, make sure it exists, otherwise build it - if has_vroot && flag.contains('.o') { - if p.os == .msvc { - build_thirdparty_obj_file_with_msvc(flag) - } - else { - build_thirdparty_obj_file(flag) - } - } - p.table.flags << flag - return - } - if hash.starts_with('include') { - if p.first_run() && !is_sig { - p.cgen.includes << '#$hash' - return - } - } - else if hash.starts_with('typedef') { - if p.first_run() { - p.cgen.typedefs << '$hash' - } - } - // TODO remove after ui_mac.m is removed - else if hash.contains('embed') { - pos := hash.index('embed') + 5 - file := hash.right(pos) - if p.pref.build_mode != BuildMode.default_mode { - p.genln('#include $file') - } - } - else if hash.contains('define') { - // Move defines on top - p.cgen.includes << '#$hash' - } - else if hash == 'v' { - println('v script') - //p.v_script = true - } - else { - if !p.can_chash { - p.error('bad token `#` (embedding C code is no longer supported)') - } - p.genln(hash) - } -} - fn (p mut Parser) if_st(is_expr bool, elif_depth int) string { if is_expr { if p.fileis('if_expr') { @@ -3272,7 +3052,7 @@ fn (p mut Parser) switch_statement() { } fn (p mut Parser) assert_statement() { - if p.first_run() { + if p.first_pass() { return } p.check(.key_assert) @@ -3442,22 +3222,6 @@ fn (p mut Parser) js_decode() string { return '' } -fn is_compile_time_const(s string) bool { - s = s.trim_space() - if s == '' { - return false - } - if s.contains('\'') { - return true - } - for c in s { - if ! ((c >= `0` && c <= `9`) || c == `.`) { - return false - } - } - return true -} - /* fn (p &Parser) building_v() bool { cur_dir := os.getwd() @@ -3495,48 +3259,3 @@ fn (p mut Parser) defer_st() { p.genln('*/') } - -/////////////////////////////////////////////////////////////////////////////// - -// fmt helpers -fn (scanner mut Scanner) fgen(s string) { - if scanner.fmt_line_empty { - s = strings.repeat(`\t`, scanner.fmt_indent) + s - } - scanner.fmt_out.write(s) - scanner.fmt_line_empty = false -} - -fn (scanner mut Scanner) fgenln(s string) { - if scanner.fmt_line_empty { - s = strings.repeat(`\t`, scanner.fmt_indent) + s - } - scanner.fmt_out.writeln(s) - scanner.fmt_line_empty = true -} - -fn (p mut Parser) fgen(s string) { - p.scanner.fgen(s) -} - -fn (p mut Parser) fspace() { - p.fgen(' ') -} - -fn (p mut Parser) fgenln(s string) { - p.scanner.fgenln(s) -} - -fn (p mut Parser) peek() Token { - for { - p.cgen.line = p.scanner.line_nr + 1 - tok := p.scanner.peek() - if tok != .nl { - return tok - } - } -} - -fn (p mut Parser) create_type_string(T Type, name string) { - p.scanner.create_type_string(T, name) -} diff --git a/compiler/scanner.v b/compiler/scanner.v index 0e1670a716..31d0e9034b 100644 --- a/compiler/scanner.v +++ b/compiler/scanner.v @@ -758,3 +758,4 @@ fn (s mut Scanner) create_type_string(T Type, name string) { s.line_nr = line s.inside_string = inside_string } + diff --git a/compiler/table.v b/compiler/table.v index 1836f1bf68..110eb229d4 100644 --- a/compiler/table.v +++ b/compiler/table.v @@ -5,12 +5,14 @@ module main import math +import strings struct Table { mut: types []Type consts []Var fns map[string]Fn + generic_fns []GenTable //map[string]GenTable // generic_fns['listen_and_serve'] == ['Blog', 'Forum'] obf_ids map[string]int // obf_ids['myfunction'] == 23 packages []string // List of all modules registered by the application imports []string // List of all imports @@ -19,6 +21,11 @@ mut: obfuscate bool } +struct GenTable { + fn_name string + types []string +} + // Holds import information scoped to the parsed file struct FileImportTable { mut: @@ -28,11 +35,11 @@ mut: } enum AccessMod { - private // private imkey_mut - private_mut // private key_mut - public // public immkey_mut (readonly) - public_mut // public, but key_mut only in this module - public_mut_mut // public and key_mut both inside and outside (not recommended to use, that's why it's so verbose) + private // private immutable + private_mut // private mutable + public // public immutable (readonly) + public_mut // public, but mutable only in this module + public_mut_mut // public and mutable both inside and outside (not recommended to use, that's why it's so verbose) } struct Type { @@ -47,6 +54,7 @@ mut: is_interface bool is_enum bool enum_vals []string + gen_types []string // This field is used for types that are not defined yet but are known to exist. // It allows having things like `fn (f Foo) bar()` before `Foo` is defined. // This information is needed in the first pass. @@ -98,6 +106,14 @@ fn (f Fn) str() string { return '$f.name($str_args) $f.typ' } +fn (t &Table) debug_fns() string { + mut s := strings.new_builder(1000) + for _, f in t.fns { + s.writeln(f.name) + } + return s.str() +} + // fn (types array_Type) print_to_file(f string) { // } const ( @@ -121,6 +137,8 @@ fn new_table(obfuscate bool) *Table { mut t := &Table { obf_ids: map[string]int{} fns: map[string]Fn{} + //generic_fns: map[string]GenTable{} + generic_fns: []GenTable obfuscate: obfuscate } t.register_type('int') @@ -142,6 +160,7 @@ fn new_table(obfuscate bool) *Table { t.register_type('bool') t.register_type('void') t.register_type('voidptr') + t.register_type('T') t.register_type('va_list') t.register_const('stdin', 'int', 'main', false) t.register_const('stdout', 'int', 'main', false) @@ -169,6 +188,66 @@ fn (t mut Table) register_package(pkg string) { t.packages << pkg } +fn (p mut Parser) register_import() { + if p.tok != .name { + p.error('bad import format') + } + mut pkg := p.lit.trim_space() + mut mod_alias := pkg + // submodule support + mut depth := 1 + p.next() + for p.tok == .dot { + p.check(.dot) + submodule := p.check_name() + mod_alias = submodule + pkg += '.' + submodule + depth++ + if depth > MaxModuleDepth { + p.error('module depth of $MaxModuleDepth exceeded: $pkg') + } + } + // aliasing (import encoding.base64 as b64) + if p.tok == .key_as && p.peek() == .name { + p.check(.key_as) + mod_alias = p.check_name() + } + // add import to file scope import table + p.import_table.register_alias(mod_alias, pkg) + // Make sure there are no duplicate imports + if p.table.imports.contains(pkg) { + return + } + p.log('adding import $pkg') + p.table.imports << pkg + p.table.register_package(pkg) + + p.fgenln(' ' + pkg) +} + + +fn (p mut Parser) register_array(typ string) { + if typ.contains('*') { + println('bad arr $typ') + return + } + if !p.table.known_type(typ) { + p.register_type_with_parent(typ, 'array') + p.cgen.typedefs << 'typedef array $typ;' + } +} + +fn (p mut Parser) register_map(typ string) { + if typ.contains('*') { + println('bad map $typ') + return + } + if !p.table.known_type(typ) { + p.register_type_with_parent(typ, 'map') + p.cgen.typedefs << 'typedef map $typ;' + } +} + fn (table &Table) known_pkg(pkg string) bool { return pkg in table.packages } @@ -679,6 +758,72 @@ fn is_valid_int_const(val, typ string) bool { return true } +fn (t mut Table) register_generic_fn(fn_name string) { + t.generic_fns << GenTable{fn_name, []string} +} + +fn (t mut Table) fn_gen_types(fn_name string) []string { + for _, f in t.generic_fns { + if f.fn_name == fn_name { + return f.types + } + } +} + +// `foo()` +// fn_name == 'foo' +// typ == 'Bar' +fn (t mut Table) register_generic_fn_type(fn_name, typ string) { + for i, f in t.generic_fns { + if f.fn_name == fn_name { + t.generic_fns[i].types << typ + return + } + } +} + +fn (p mut Parser) typ_to_fmt(typ string, level int) string { + t := p.table.find_type(typ) + if t.is_enum { + return '%d' + } + switch typ { + case 'string': return '%.*s' + case 'ustring': return '%.*s' + case 'byte', 'int', 'char', 'byte', 'bool', 'u32', 'i32', 'i16', 'u16', 'i8', 'u8': return '%d' + case 'f64', 'f32': return '%f' + case 'i64', 'u64': return '%lld' + case 'byte*', 'byteptr': return '%s' + // case 'array_string': return '%s' + // case 'array_int': return '%s' + case 'void': p.error('cannot interpolate this value') + default: + if typ.ends_with('*') { + return '%p' + } + } + if t.parent != '' && level == 0 { + return p.typ_to_fmt(t.parent, level+1) + } + return '' +} + +fn is_compile_time_const(s string) bool { + s = s.trim_space() + if s == '' { + return false + } + if s.contains('\'') { + return true + } + for c in s { + if ! ((c >= `0` && c <= `9`) || c == `.`) { + return false + } + } + return true +} + // Once we have a module format we can read from module file instead // this is not optimal fn (table &Table) qualify_module(mod string, file_path string) string { diff --git a/compiler/vfmt.v b/compiler/vfmt.v new file mode 100644 index 0000000000..8a6453efb1 --- /dev/null +++ b/compiler/vfmt.v @@ -0,0 +1,54 @@ +// 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 + +// fmt helpers +fn (scanner mut Scanner) fgen(s string) { + if scanner.fmt_line_empty { + s = strings.repeat(`\t`, scanner.fmt_indent) + s + } + scanner.fmt_out.write(s) + scanner.fmt_line_empty = false +} + +fn (scanner mut Scanner) fgenln(s string) { + if scanner.fmt_line_empty { + s = strings.repeat(`\t`, scanner.fmt_indent) + s + } + scanner.fmt_out.writeln(s) + scanner.fmt_line_empty = true +} + +fn (p mut Parser) fgen(s string) { + p.scanner.fgen(s) +} + +fn (p mut Parser) fspace() { + p.fgen(' ') +} + +fn (p mut Parser) fgenln(s string) { + p.scanner.fgenln(s) +} + +fn (p mut Parser) peek() Token { + for { + tok := p.scanner.peek() + if tok != .nl { + return tok + } + } +} + +fn (p mut Parser) fmt_inc() { + p.scanner.fmt_indent++ +} + +fn (p mut Parser) fmt_dec() { + p.scanner.fmt_indent-- +} + diff --git a/vlib/builtin/option.v b/vlib/builtin/option.v index 8e7c84d263..bb60a32e73 100644 --- a/vlib/builtin/option.v +++ b/vlib/builtin/option.v @@ -13,7 +13,7 @@ struct Option { // `fn foo() ?Foo { return foo }` => `fn foo() ?Foo { return opt_ok(foo); }` fn opt_ok(data voidptr, size int) Option { if size > 255 { - panic('option size too big: $size (max is 255)') + panic('option size too big: $size (max is 255), this is a temporary limit') } res := Option { ok: true diff --git a/vlib/crypto/sha512/sha512.v b/vlib/crypto/sha512/sha512.v index e908ba8ec8..74d6f9341d 100644 --- a/vlib/crypto/sha512/sha512.v +++ b/vlib/crypto/sha512/sha512.v @@ -296,4 +296,4 @@ pub fn (d &Digest) size() int { } } -pub fn (d &Digest) block_size() int { return BlockSize } \ No newline at end of file +pub fn (d &Digest) block_size() int { return BlockSize } diff --git a/vlib/http/http_nix.v b/vlib/http/http_nix.v index 1b07175476..8a1f6929ff 100644 --- a/vlib/http/http_nix.v +++ b/vlib/http/http_nix.v @@ -189,11 +189,11 @@ pub fn (req &Request) do() Response { } } -fn unescape(s string) string { +pub fn unescape(s string) string { return string(byteptr(C.curl_unescape(s.str, s.len))) } -fn escape(s string) string { +pub fn escape(s string) string { return string(byteptr(C.curl_escape(s.str, s.len))) } diff --git a/vlib/math/stats/stats.v b/vlib/math/stats/stats.v index 0023189c80..ea2584d6b4 100644 --- a/vlib/math/stats/stats.v +++ b/vlib/math/stats/stats.v @@ -248,4 +248,4 @@ pub fn range(arr []f64) f64 { return f64(0) } return max(arr) - min(arr) -} \ No newline at end of file +} diff --git a/vlib/net/socket.v b/vlib/net/socket.v index 5b9d9ea1cd..e6ef6146e5 100644 --- a/vlib/net/socket.v +++ b/vlib/net/socket.v @@ -79,6 +79,10 @@ pub fn socket(family int, _type int, proto int) ?Socket { } sockfd := C.socket(family, _type, proto) + one:=1 + // This is needed so that there are no problems with reusing the + // same port after the application exits. + C.setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) if sockfd == 0 { return error('socket: init failed') } @@ -117,11 +121,12 @@ pub fn (s Socket) bind(port int) ?int { // put socket into passive mode and wait to receive pub fn (s Socket) listen() ?int { backlog := 128 - res := C.listen(s.sockfd, backlog) + res := int(C.listen(s.sockfd, backlog)) if res < 0 { return error('socket: listen failed') } - return int(res) +println('liisten res = $res') + return res } // put socket into passive mode with user specified backlog and wait to receive @@ -139,6 +144,7 @@ pub fn (s Socket) listen_backlog(backlog int) ?int { // helper method to create, bind, and listen given port number pub fn listen(port int) ?Socket { +println('net.listen($port)') s := socket(AF_INET, SOCK_STREAM, 0) or { return error(err) } @@ -153,6 +159,7 @@ pub fn listen(port int) ?Socket { // accept first connection request from socket queue pub fn (s Socket) accept() ?Socket { +println('accept()') addr := C.sockaddr_storage{} size := 128 // sizeof(sockaddr_storage) sockfd := C.accept(s.sockfd, &addr, &size) @@ -251,3 +258,47 @@ pub fn (s Socket) close() ?int { return 0 } + +const ( + MAX_READ = 400 +) +pub fn (s Socket) write(str string) { + line := '$str\r\n' + C.write(s.sockfd, line.str, line.len) +} + +pub fn (s Socket) read_line() string { + mut res := '' + for { + println('.') + mut buf := malloc(MAX_READ) + n := int(C.recv(s.sockfd, buf, MAX_READ-1, 0)) + println('numbytes=$n') + if n == -1 { + println('recv failed') + // TODO + return '' + } + if n == 0 { + break + } + // println('resp len=$numbytes') + buf[n] = `\0` + // C.printf('!!buf= "%s" n=%d\n', buf,n) + line := string(buf) + res += line + // Reached a newline. That's an end of an IRC message + // TODO dont need ends_with check ? + if line.ends_with('\n') || n < MAX_READ - 1 { + // println('NL') + break + } + if line.ends_with('\r\n') { + // println('RNL') + break + } + } + return res +} + + diff --git a/vlib/os/os_nix.v b/vlib/os/os_nix.v index 56c82ea511..f20415fc80 100644 --- a/vlib/os/os_nix.v +++ b/vlib/os/os_nix.v @@ -14,4 +14,4 @@ pub fn get_error_msg(code int) string { return '' } return tos(_ptr_text, C.strlen(_ptr_text)) -} \ No newline at end of file +} diff --git a/vlib/os/os_win.v b/vlib/os/os_win.v index 75b0034c75..9b5a84e68b 100644 --- a/vlib/os/os_win.v +++ b/vlib/os/os_win.v @@ -90,4 +90,4 @@ pub fn get_error_msg(code int) string { return '' } return tos(_ptr_text, C.strlen(_ptr_text)) -} \ No newline at end of file +} diff --git a/vlib/time/time.v b/vlib/time/time.v index 1107b14180..42f4b15a04 100644 --- a/vlib/time/time.v +++ b/vlib/time/time.v @@ -56,10 +56,110 @@ pub fn random() Time { } } -pub fn unix(u int) Time { - mut t := &C.tm{!} - t = C.localtime(&u) - return convert_ctime(t) +const ( +// The unsigned zero year for internal calculations. + // Must be 1 mod 400, and times before it will not compute correctly, + // but otherwise can be changed at will. + absoluteZeroYear = i64(-292277022399) + + secondsPerMinute = 60 + secondsPerHour = 60 * secondsPerMinute + secondsPerDay = 24 * secondsPerHour + secondsPerWeek = 7 * secondsPerDay + daysPer400Years = 365*400 + 97 + daysPer100Years = 365*100 + 24 + daysPer4Years = 365*4 + 1 + + daysBefore = [ + 0, + 31, + 31 + 28, + 31 + 28 + 31, + 31 + 28 + 31 + 30, + 31 + 28 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, +] + +) + + +// Based on Go's time package. +// Copyright 2009 The Go Authors. +pub fn unix(abs int) Time { + // Split into time and day. + mut d := abs / secondsPerDay + + // Account for 400 year cycles. + mut n := d / daysPer400Years + mut y := 400 * n + d -= daysPer400Years * n + + // Cut off 100-year cycles. + // The last cycle has one extra leap year, so on the last day + // of that year, day / daysPer100Years will be 4 instead of 3. + // Cut it back down to 3 by subtracting n>>2. + n = d / daysPer100Years + n -= n >> 2 + y += 100 * n + d -= daysPer100Years * n + + // Cut off 4-year cycles. + // The last cycle has a missing leap year, which does not + // affect the computation. + n = d / daysPer4Years + y += 4 * n + d -= daysPer4Years * n + + // Cut off years within a 4-year cycle. + // The last year is a leap year, so on the last day of that year, + // day / 365 will be 4 instead of 3. Cut it back down to 3 + // by subtracting n>>2. + n = d / 365 + n -= n >> 2 + y += n + d -= 365 * n + + yday := int(d) + mut day := yday + + year := abs / int(3.154e+7) + 1970 //int(i64(y) + absoluteZeroYear) + hour := int(abs%secondsPerDay) / secondsPerHour + minute := int(abs % secondsPerHour) / secondsPerMinute + second := int(abs % secondsPerMinute) + + if is_leap_year(year) { + // Leap year + if day > 31+29-1 { + // After leap day; pretend it wasn't there. + day-- + } else if day == 31+29-1 { + // Leap day. + day = 29 + return Time{year:year, month:2, day:day, hour:hour, minute: minute, second: second} + } + } + + // Estimate month on assumption that every month has 31 days. + // The estimate may be too low by at most one month, so adjust. + mut month := day / 31 + mut begin := 0 + end := int(daysBefore[month+1]) + if day >= end { + month++ + begin = end + } else { + begin = int(daysBefore[month]) + } + + month++ // because January is 1 + day = day - begin + 1 + return Time{year:year, month: month, day:day, hour:hour, minute: minute, second: second} } pub fn convert_ctime(t tm) Time { diff --git a/vlib/time/time_test.v b/vlib/time/time_test.v index e8f2ce6941..8759d4f321 100644 --- a/vlib/time/time_test.v +++ b/vlib/time/time_test.v @@ -38,3 +38,16 @@ fn test_days_in_month() { assert check(11, 2001, 30) // November assert check(12, 2001, 31) // December } + + +fn test_unix() { + t := time.unix(1564366499) + assert t.year == 2019 + assert t.month == 7 + assert t.day == 29 + assert t.hour == 2 + assert t.minute == 14 + //assert t.second == 32 // TODO broken +} + + diff --git a/vlib/vweb/tmpl/tmpl.v b/vlib/vweb/tmpl/tmpl.v new file mode 100644 index 0000000000..9ae2f49ced --- /dev/null +++ b/vlib/vweb/tmpl/tmpl.v @@ -0,0 +1,69 @@ +module tmpl + +import os +import strings + +const ( + STR_START = 'sb.write(\'' + STR_END = '\' ) ' +) + + +pub fn compile_template(path string) string { + //lines := os.read_lines(path) + mut html := os.read_file(path) or { + panic('html failed') + return '' + } + mut header := '' + if os.file_exists('header.html') { + h := os.read_file('header.html') or { + panic('html failed') + return '' + } + header = h + } + lines := html.split_into_lines() + mut s := strings.new_builder(1000) + base := path.all_after('/').replace('.html', '') + s.writeln('module main import strings fn ${base}_view() string { // this line will get removed becase only function body is embedded +mut sb := strings.new_builder(${lines.len * 30}) +header := \'$header\' +_ := header +//footer := \'footer\' +') + s.writeln(STR_START) + for line in lines { + if line.contains('@if ') { + s.writeln(STR_END) + pos := line.index('@if') + s.writeln('if ' + line.right(pos + 4) + '{') + s.writeln(STR_START) + } + else if line.contains('@end') { + s.writeln(STR_END) + s.writeln('}') + s.writeln(STR_START) + } + else if line.contains('@else') { + s.writeln(STR_END) + s.writeln(' } else { ') + s.writeln(STR_START) + } + else if line.contains('@for') { + s.writeln(STR_END) + pos := line.index('@for') + s.writeln('for ' + line.right(pos + 4) + '{') + s.writeln(STR_START) + } + // @name + else { + s.writeln(line.replace('@', '\x24').replace('\'', '"') ) + } + } + s.writeln(STR_END) + s.writeln('tmpl_res := sb.str() ') + s.writeln('return tmpl_res }') + return s.str() +} + diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v new file mode 100644 index 0000000000..7ee109a89f --- /dev/null +++ b/vlib/vweb/vweb.v @@ -0,0 +1,121 @@ +module vweb + +import ( + os + strings + net + http +) + +struct Context { +pub: + req http.Request + conn net.Socket + post_form map[string]string + // TODO Response + headers []string +} + +pub fn (ctx Context) write(s string) { + //ctx.conn.write(s) +} + +pub fn (ctx Context) redirect(url string) { + h := ctx.headers.join('\n') + ctx.conn.write(' +HTTP/1.1 302 Found +Location: $url +$h +') +} + +pub fn (ctx mut Context) set_cookie(key, val string) { + ctx.set_header('Set-Cookie', '$key=$val') +} + +pub fn (ctx Context) get_cookie(key string) string { + cookie := ctx.req.headers['Cookie'] + return cookie.find_between('$key=', ';') +} + +fn (ctx mut Context) set_header(key, val string) { + // ctx.resp.headers[key] = val + ctx.headers << '$key: $val' +} + +pub fn (ctx Context) html(html string) { + //tmpl := os.read_file(path) or {return} + ctx.conn.write('HTTP/1.1 200 OK +Content-Type: text/html + +$html +') + +} + +pub fn run(port int) { + l := net.listen(port) or { panic('failed to listen') return } + for { + conn := l.accept() or { + panic('accept() failed') + return + } + // TODO move this to handle_conn(conn, app) + s := conn.read_line() + // Parse the first line + // "GET / HTTP/1.1" + first_line := s.all_before('\n') + vals := first_line.split(' ') + mut action := vals[1].right(1).all_before('/') + if action == '' { + action = 'index' + } + req := http.Request{ + headers: map[string]string{} + ws_func: 0 + user_ptr: 0 + method: vals[0] + url: vals[1] + } + mut app := T{ + vweb: Context{ + req: req + conn: conn + post_form: map[string]string{} + } + } + app.init() + if req.method == 'POST' { + app.vweb.parse_form(s) + } + println('vweb action = "$action"') + if vals.len < 2 { + println('no vals for http') + return + } + app.$action() + conn.close() + } +} + + +fn (ctx mut Context) parse_form(s string) { + if ctx.req.method != 'POST' { + return + } + pos := s.index('\r\n\r\n') + if pos > -1 { + mut str_form := s.substr(pos, s.len) + str_form = str_form.replace('+', ' ') + words := str_form.split('&') + for word in words { + keyval := word.split('=') + key := keyval[0] + val := keyval[1] + //println('http form $key => $val') + ctx.post_form[key] = http.unescape(val) + } + } +} + +