From 8dfb14b1c450b16ebde600505386b4d376043454 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 1 Apr 2020 21:24:58 +0200 Subject: [PATCH] remove 15k lines of code of the old backend; make V2 the default backend --- cmd/tools/modules/testing/common.v | 4 +- .../compiler => cmd/v/internal/compile}/cc.v | 60 +- .../v/internal/compile}/cflags.v | 8 +- cmd/v/internal/compile/compile.v | 346 +- .../v/internal/compile}/live.v | 11 +- .../v/internal/compile}/msvc.v | 2 +- cmd/v/simple_tool.v | 12 +- cmd/v/v.v | 19 +- vlib/compiler/aparser.v | 3256 ----------------- vlib/compiler/asm.v | 35 - vlib/compiler/cgen.v | 534 --- vlib/compiler/cheaders.v | 344 -- vlib/compiler/compile_errors.v | 317 -- vlib/compiler/compiler_options.v | 26 - vlib/compiler/comptime.v | 612 ---- vlib/compiler/depgraph.v | 153 - vlib/compiler/enum.v | 159 - vlib/compiler/expression.v | 958 ----- vlib/compiler/fn.v | 1742 --------- vlib/compiler/for.v | 227 -- vlib/compiler/gen_c.v | 757 ---- vlib/compiler/gen_js.v | 260 -- vlib/compiler/gen_x64.v | 3 - vlib/compiler/get_type.v | 255 -- vlib/compiler/if_match.v | 368 -- vlib/compiler/json_gen.v | 162 - vlib/compiler/main.v | 963 ----- vlib/compiler/modules.v | 219 -- vlib/compiler/optimization.v | 48 - vlib/compiler/query.v | 340 -- vlib/compiler/scanner.v | 1021 ------ vlib/compiler/string_expression.v | 162 - vlib/compiler/struct.v | 536 --- vlib/compiler/table.v | 1185 ------ vlib/compiler/token.v | 310 -- vlib/compiler/vfmt.v | 310 -- vlib/compiler/vtmp.v | 21 - vlib/v/gen/cgen.v | 17 + 38 files changed, 462 insertions(+), 15300 deletions(-) rename {vlib/compiler => cmd/v/internal/compile}/cc.v (93%) rename {vlib/compiler => cmd/v/internal/compile}/cflags.v (99%) rename {vlib/compiler => cmd/v/internal/compile}/live.v (99%) rename {vlib/compiler => cmd/v/internal/compile}/msvc.v (99%) delete mode 100644 vlib/compiler/aparser.v delete mode 100644 vlib/compiler/asm.v delete mode 100644 vlib/compiler/cgen.v delete mode 100644 vlib/compiler/cheaders.v delete mode 100644 vlib/compiler/compile_errors.v delete mode 100644 vlib/compiler/compiler_options.v delete mode 100644 vlib/compiler/comptime.v delete mode 100644 vlib/compiler/depgraph.v delete mode 100644 vlib/compiler/enum.v delete mode 100644 vlib/compiler/expression.v delete mode 100644 vlib/compiler/fn.v delete mode 100644 vlib/compiler/for.v delete mode 100644 vlib/compiler/gen_c.v delete mode 100644 vlib/compiler/gen_js.v delete mode 100644 vlib/compiler/gen_x64.v delete mode 100644 vlib/compiler/get_type.v delete mode 100644 vlib/compiler/if_match.v delete mode 100644 vlib/compiler/json_gen.v delete mode 100644 vlib/compiler/main.v delete mode 100644 vlib/compiler/modules.v delete mode 100644 vlib/compiler/optimization.v delete mode 100644 vlib/compiler/query.v delete mode 100644 vlib/compiler/scanner.v delete mode 100644 vlib/compiler/string_expression.v delete mode 100644 vlib/compiler/struct.v delete mode 100644 vlib/compiler/table.v delete mode 100644 vlib/compiler/token.v delete mode 100644 vlib/compiler/vfmt.v delete mode 100644 vlib/compiler/vtmp.v diff --git a/cmd/tools/modules/testing/common.v b/cmd/tools/modules/testing/common.v index 5c1ecc0618..05605d57b7 100644 --- a/cmd/tools/modules/testing/common.v +++ b/cmd/tools/modules/testing/common.v @@ -74,7 +74,9 @@ pub fn (ts mut TestSession) test() { } ts.files = remaining_files ts.benchmark.set_total_expected_steps(remaining_files.len) - mut pool_of_test_runners := sync.new_pool_processor({ + // QTODO + //mut pool_of_test_runners := sync.new_pool_processor({ + mut pool_of_test_runners := sync.new_pool_processor(sync.PoolProcessorConfig{ callback: worker_trunner }) pool_of_test_runners.set_shared_context(ts) diff --git a/vlib/compiler/cc.v b/cmd/v/internal/compile/cc.v similarity index 93% rename from vlib/compiler/cc.v rename to cmd/v/internal/compile/cc.v index 9fc46f86fa..e1b0970974 100644 --- a/vlib/compiler/cc.v +++ b/cmd/v/internal/compile/cc.v @@ -1,7 +1,7 @@ // Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. -module compiler +module compile import ( os @@ -10,6 +10,15 @@ import ( term ) + pub const ( + v_version = '0.1.26' + ) + + +const ( +v_modules_path = pref.default_module_path +) + fn todo() { } @@ -219,6 +228,8 @@ fn (v mut V) cc() { a << '-c' } else if v.pref.is_cache { + /* + QTODO builtin_o_path := os.join_path(v_modules_path, 'cache', 'vlib', 'builtin.o') a << builtin_o_path.replace('builtin.o', 'strconv.o') // TODO hack no idea why this is needed if os.exists(builtin_o_path) { @@ -260,7 +271,9 @@ fn (v mut V) cc() { a << '-framework Cocoa -framework Carbon' } } + */ } + if v.pref.sanitize { a << '-fsanitize=leak' } @@ -367,7 +380,7 @@ start: ================== C error. This should never happen. -V compiler version: V $Version $vhash() +V compiler version: V $v_version $vhash() Host OS: ${pref.get_host_os().str()} Target OS: $v.pref.os.str() @@ -463,6 +476,8 @@ If you're confident that all of the above is true, please try running V with the } fn (c mut V) cc_windows_cross() { + /* + QTODO println('Cross compiling for Windows...') if !c.pref.out_name.ends_with('.exe') { c.pref.out_name += '.exe' @@ -533,6 +548,7 @@ fn (c mut V) cc_windows_cross() { } */ println('Done!') + */ } fn (c &V) build_thirdparty_obj_files() { @@ -549,6 +565,38 @@ fn (c &V) build_thirdparty_obj_files() { } } +fn (v &V) build_thirdparty_obj_file(path string, moduleflags []CFlag) { + obj_path := os.real_path(path) + if os.exists(obj_path) { + return + } + println('$obj_path not found, building it...') + parent := os.dir(obj_path) + files := os.ls(parent)or{ + panic(err) + } + mut cfiles := '' + for file in files { + if file.ends_with('.c') { + cfiles += '"' + os.real_path(parent + os.path_separator + file) + '" ' + } + } + btarget := moduleflags.c_options_before_target() + atarget := moduleflags.c_options_after_target() + cmd := '$v.pref.ccompiler $v.pref.third_party_option $btarget -c -o "$obj_path" $cfiles $atarget ' + res := os.exec(cmd)or{ + println('failed thirdparty object build cmd: $cmd') + verror(err) + return + } + if res.exit_code != 0 { + println('failed thirdparty object build cmd: $cmd') + verror(res.output) + return + } + println(res.output) +} + fn missing_compiler_info() string { $if windows { return 'https://github.com/vlang/v/wiki/Installing-a-C-compiler-on-Windows' @@ -578,3 +626,11 @@ fn error_context_lines(text, keyword string, before, after int) []string { idx_e := if idx_s + after < lines.len { idx_s + after } else { lines.len } return lines[idx_s..idx_e] } + +fn vhash() string { + mut buf := [50]byte + buf[0] = 0 + C.snprintf(charptr(buf), 50, '%s', C.V_COMMIT_HASH) + return tos_clone(buf) +} + diff --git a/vlib/compiler/cflags.v b/cmd/v/internal/compile/cflags.v similarity index 99% rename from vlib/compiler/cflags.v rename to cmd/v/internal/compile/cflags.v index fb33e69412..45b6e3c843 100644 --- a/vlib/compiler/cflags.v +++ b/cmd/v/internal/compile/cflags.v @@ -1,7 +1,7 @@ // Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. -module compiler +module compile import os // parsed cflag @@ -24,6 +24,8 @@ fn (v &V) get_os_cflags() []CFlag { ctimedefines << v.pref.compile_defines } +// QTODO +/* for flag in v.table.cflags { if flag.os == '' || (flag.os == 'linux' && v.pref.os == .linux) || (flag.os == 'darwin' && v.pref.os == .mac) || (flag.os == 'freebsd' && v.pref.os == .freebsd) || (flag.os == 'windows' && v.pref.os == .windows) || (flag.os == 'mingw' && v.pref.os == .windows && v.pref.ccompiler != 'msvc') || (flag.os == 'solaris' && v.pref.os == .solaris) { flags << flag @@ -32,6 +34,7 @@ fn (v &V) get_os_cflags() []CFlag { flags << flag } } + */ return flags } @@ -63,6 +66,8 @@ fn (cf &CFlag) format() string { } // check if cflag is in table +/* +QTODO fn (table &Table) has_cflag(cflag CFlag) bool { for cf in table.cflags { if cf.os == cflag.os && cf.name == cflag.name && cf.value == cflag.value { @@ -146,6 +151,7 @@ fn (table mut Table) parse_cflag(cflag string, mod string, ctimedefines []string } return true } +*/ // TODO: implement msvc specific c_options_before_target and c_options_after_target ... fn (cflags []CFlag) c_options_before_target_msvc() string { diff --git a/cmd/v/internal/compile/compile.v b/cmd/v/internal/compile/compile.v index a2694fd5c4..46b31303f8 100644 --- a/cmd/v/internal/compile/compile.v +++ b/cmd/v/internal/compile/compile.v @@ -5,16 +5,127 @@ module compile import ( benchmark - compiler os + v.builder + v.pref + strings ) +pub struct V { +pub mut: + mod_file_cacher &builder.ModFileCacher // used during lookup for v.mod to support @VROOT + out_name_c string // name of the temporary C file + files []string // all V files that need to be parsed and compiled + compiled_dir string // contains os.real_path() of the dir of the final file beeing compiled, or the dir itself when doing `v .` + pref &pref.Preferences // all the preferences and settings extracted to a struct for reusability + vgen_buf strings.Builder // temporary buffer for generated V code (.str() etc) + file_parser_idx map[string]int // map absolute file path to v.parsers index + gen_parser_idx map[string]int + cached_mods []string + module_lookup_paths []string + + v_fmt_all bool // << input set by cmd/tools/vfmt.v + v_fmt_file string // << file given by the user from cmd/tools/vfmt.v + v_fmt_file_result string // >> file with formatted output generated by vlib/compiler/vfmt.v +} + +pub fn new_v(pref &pref.Preferences) &V { + rdir := os.real_path(pref.path) + + mut out_name_c := get_vtmp_filename(pref.out_name, '.tmp.c') + if pref.is_so { + out_name_c = get_vtmp_filename(pref.out_name, '.tmp.so.c') + } + + mut vgen_buf := strings.new_builder(1000) + vgen_buf.writeln('module vgen\nimport strings') + compiled_dir:=if os.is_dir(rdir) { rdir } else { os.dir(rdir) } + + return &V{ + mod_file_cacher: builder.new_mod_file_cacher() + compiled_dir:compiled_dir// if os.is_dir(rdir) { rdir } else { os.dir(rdir) } + out_name_c: out_name_c + pref: pref + vgen_buf: vgen_buf + } +} + + +// make v2 from v1 +fn (v &V) new_v2() builder.Builder { + mut b := builder.new_builder(v.pref) + b = { b| + os: v.pref.os, + module_path: pref.default_module_path, + compiled_dir: v.compiled_dir, + module_search_paths: v.module_lookup_paths + } + return b +} + +fn get_vtmp_folder() string { + vtmp := os.join_path(os.temp_dir(), 'v') + if !os.is_dir(vtmp) { + os.mkdir(vtmp) or { + panic(err) + } + } + return vtmp +} + +fn get_vtmp_filename(base_file_name string, postfix string) string { + vtmp := get_vtmp_folder() + return os.real_path(os.join_path(vtmp, os.file_name(os.real_path(base_file_name)) + postfix)) +} + + +pub fn (v mut V) compile_x64() { + $if !linux { + println('v -x64 can only generate Linux binaries for now') + println('You are not on a Linux system, so you will not ' + 'be able to run the resulting executable') + } + //v.files << v.v_files_from_dir(os.join_path(v.pref.vlib_path,'builtin','bare')) + v.files << v.pref.path + v.set_module_lookup_paths() + mut b := v.new_v2() + // move all this logic to v2 + b.build_x64(v.files, v.pref.out_name) +} + + +pub fn (v mut V) compile2() { + if os.user_os() != 'windows' && v.pref.ccompiler == 'msvc' { + verror('Cannot build with msvc on ${os.user_os()}') + } + //cgen.genln('// Generated by V') + //println('compile2()') + if v.pref.verbosity.is_higher_or_equal(.level_three) { + println('all .v files before:') + println(v.files) + } + // v1 compiler files + //v.add_v_files_to_compile() + //v.files << v.dir + // v2 compiler + v.files << v.get_builtin_files() + v.files << v.get_user_files() + v.set_module_lookup_paths() + if v.pref.verbosity.is_higher_or_equal(.level_three) { + println('all .v files:') + println(v.files) + } + mut b := v.new_v2() + b.build_c(v.files, v.out_name_c)// v.pref.out_name + '.c') + v.cc() +} + + pub fn compile(command string, args []string) { // Construct the V object from command line arguments parse_and_output_new_format(args) prefs, remaining := parse_arguments(args) check_for_common_mistake(args, prefs) - mut v := compiler.new_v(prefs) + mut v := new_v(prefs) if v.pref.verbosity.is_higher_or_equal(.level_two) { println(args) } @@ -26,7 +137,8 @@ pub fn compile(command string, args []string) { v.compile2() } else { - v.compile() + //v.compile() + v.compile2() } if v.pref.is_stats { tmark.stop() @@ -35,10 +147,10 @@ pub fn compile(command string, args []string) { if v.pref.is_test || v.pref.is_run { run_compiled_executable_and_exit(v, remaining) } - v.finalize_compilation() + //v.finalize_compilation() } -pub fn run_compiled_executable_and_exit(v &compiler.V, remaining_args []string) { +pub fn run_compiled_executable_and_exit(v &V, remaining_args []string) { if v.pref.verbosity.is_higher_or_equal(.level_two) { println('============ running $v.pref.out_name ============') } @@ -70,3 +182,227 @@ pub fn run_compiled_executable_and_exit(v &compiler.V, remaining_args []string) } exit(0) } + +// 'strings' => 'VROOT/vlib/strings' +// 'installed_mod' => '~/.vmodules/installed_mod' +// 'local_mod' => '/path/to/current/dir/local_mod' +fn (v mut V) set_module_lookup_paths() { + // Module search order: + // 0) V test files are very commonly located right inside the folder of the + // module, which they test. Adding the parent folder of the module folder + // with the _test.v files, *guarantees* that the tested module can be found + // without needing to set custom options/flags. + // 1) search in the *same* directory, as the compiled final v program source + // (i.e. the . in `v .` or file.v in `v file.v`) + // 2) search in the modules/ in the same directory. + // 3) search in the provided paths + // By default, these are what (3) contains: + // 3.1) search in vlib/ + // 3.2) search in ~/.vmodules/ (i.e. modules installed with vpm) + v.module_lookup_paths = [] + if v.pref.is_test { + v.module_lookup_paths << os.base_dir(v.compiled_dir) // pdir of _test.v + } + v.module_lookup_paths << v.compiled_dir + x := os.join_path(v.compiled_dir, 'modules') + if v.pref.verbosity.is_higher_or_equal(.level_two) { + println('x: "$x"') + } + v.module_lookup_paths << os.join_path(v.compiled_dir, 'modules') + v.module_lookup_paths << v.pref.lookup_path + if v.pref.verbosity.is_higher_or_equal(.level_two) { + v.log('v.module_lookup_paths') //: $v.module_lookup_paths') + println(v.module_lookup_paths) + } +} + + + pub fn verror(s string) { + println('V error: $s') + os.flush() + exit(1) + } + +pub fn (v &V) get_builtin_files() []string { + // Lookup for built-in folder in lookup path. + // Assumption: `builtin/` folder implies usable implementation of builtin + for location in v.pref.lookup_path { + if !os.exists(os.join_path(location, 'builtin')) { + continue + } + if v.pref.is_bare { + return v.v_files_from_dir(os.join_path(location, 'builtin', 'bare')) + } + $if js { + return v.v_files_from_dir(os.join_path(location, 'builtin', 'js')) + } + return v.v_files_from_dir(os.join_path(location, 'builtin')) + } + // Panic. We couldn't find the folder. + verror('`builtin/` not included on module lookup path. +Did you forget to add vlib to the path? (Use @vlib for default vlib)') + panic('Unreachable code reached.') +} + + +pub fn (v &V) get_user_files() []string { + mut dir := v.pref.path + v.log('get_v_files($dir)') + // Need to store user files separately, because they have to be added after + // libs, but we dont know which libs need to be added yet + mut user_files := []string + + // See cmd/tools/preludes/README.md for more info about what preludes are + vroot := os.dir(pref.vexe_path()) + preludes_path := os.join_path(vroot, 'cmd', 'tools', 'preludes') + if v.pref.is_live { + user_files << os.join_path(preludes_path, 'live_main.v') + } + if v.pref.is_solive { + user_files << os.join_path(preludes_path, 'live_shared.v') + } + if v.pref.is_test { + user_files << os.join_path(preludes_path, 'tests_assertions.v') + } + if v.pref.is_test && v.pref.is_stats { + user_files << os.join_path(preludes_path, 'tests_with_stats.v') + } + + is_test := dir.ends_with('_test.v') + mut is_internal_module_test := false + if is_test { + tcontent := os.read_file(dir)or{ + panic('$dir does not exist') + } + slines := tcontent.trim_space().split_into_lines() + for sline in slines { + line := sline.trim_space() + if line.len > 2 { + if line[0] == `/` && line[1] == `/` { + continue + } + if line.starts_with('module ') && !line.starts_with('module main') { + is_internal_module_test = true + break + } + } + } + } + if is_internal_module_test { + // v volt/slack_test.v: compile all .v files to get the environment + single_test_v_file := os.real_path(dir) + if v.pref.verbosity.is_higher_or_equal(.level_two) { + v.log('> Compiling an internal module _test.v file $single_test_v_file .') + v.log('> That brings in all other ordinary .v files in the same module too .') + } + user_files << single_test_v_file + dir = os.base_dir(single_test_v_file) + } + is_real_file := os.exists(dir) && !os.is_dir(dir) + if is_real_file && ( dir.ends_with('.v') || dir.ends_with('.vsh') ) { + single_v_file := dir + // Just compile one file and get parent dir + user_files << single_v_file + if v.pref.verbosity.is_higher_or_equal(.level_two) { + v.log('> just compile one file: "${single_v_file}"') + } + } + else { + if v.pref.verbosity.is_higher_or_equal(.level_two) { + v.log('> add all .v files from directory "${dir}" ...') + } + // Add .v files from the directory being compiled + files := v.v_files_from_dir(dir) + for file in files { + user_files << file + } + } + if user_files.len == 0 { + println('No input .v files') + exit(1) + } + if v.pref.verbosity.is_higher_or_equal(.level_two) { + v.log('user_files: $user_files') + } + return user_files +} +pub fn (v &V) log(s string) { + if !v.pref.verbosity.is_higher_or_equal(.level_two) { + return + } + println(s) +} + + +pub fn (v &V) v_files_from_dir(dir string) []string { + mut res := []string + if !os.exists(dir) { + if dir == 'compiler' && os.is_dir('vlib') { + println('looks like you are trying to build V with an old command') + println('use `v -o v cmd/v` instead of `v -o v compiler`') + } + verror("$dir doesn't exist") + } + else if !os.is_dir(dir) { + verror("$dir isn't a directory!") + } + mut files := os.ls(dir)or{ + panic(err) + } + if v.pref.verbosity.is_higher_or_equal(.level_three) { + println('v_files_from_dir ("$dir")') + } + files.sort() + for file in files { + if !file.ends_with('.v') && !file.ends_with('.vh') { + continue + } + if file.ends_with('_test.v') { + continue + } + if (file.ends_with('_win.v') || file.ends_with('_windows.v')) && v.pref.os != .windows { + continue + } + if (file.ends_with('_lin.v') || file.ends_with('_linux.v')) && v.pref.os != .linux { + continue + } + if (file.ends_with('_mac.v') || file.ends_with('_darwin.v')) && v.pref.os != .mac { + continue + } + if file.ends_with('_nix.v') && v.pref.os == .windows { + continue + } + if file.ends_with('_android.v') && v.pref.os != .android { + continue + } + if file.ends_with('_freebsd.v') && v.pref.os != .freebsd { + continue + } + if file.ends_with('_solaris.v') && v.pref.os != .solaris { + continue + } + if file.ends_with('_js.v') && v.pref.os != .js { + continue + } + if file.ends_with('_c.v') && v.pref.os == .js { + continue + } + if v.pref.compile_defines_all.len > 0 && file.contains('_d_') { + mut allowed := false + for cdefine in v.pref.compile_defines { + file_postfix := '_d_${cdefine}.v' + if file.ends_with(file_postfix) { + allowed = true + break + } + } + if !allowed { + continue + } + } + res << os.join_path(dir, file) + } + return res +} + + diff --git a/vlib/compiler/live.v b/cmd/v/internal/compile/live.v similarity index 99% rename from vlib/compiler/live.v rename to cmd/v/internal/compile/live.v index 61caf2a0aa..b58a06492a 100644 --- a/vlib/compiler/live.v +++ b/cmd/v/internal/compile/live.v @@ -1,4 +1,4 @@ -module compiler +module compile import ( os @@ -20,6 +20,8 @@ fn (v &V) generate_hotcode_reloading_compiler_flags() []string { } fn (v &V) generate_hotcode_reloading_declarations() { + /* + QTODO mut cgen := v.cgen if v.pref.os != .windows { if v.pref.is_so { @@ -45,9 +47,12 @@ void pthread_mutex_unlock(HANDLE *m) { cgen.genln('HANDLE live_fn_mutex = 0;') } } + */ } fn (v &V) generate_hotcode_reloading_main_caller() { + // QTODO + /* if !v.pref.is_live { return } @@ -72,9 +77,12 @@ fn (v &V) generate_hotcode_reloading_main_caller() { 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() { + /* + QTODO mut cgen := v.cgen // Hot code reloading if v.pref.is_live { @@ -246,4 +254,5 @@ void reload_so() { if v.pref.is_so { cgen.genln(' int load_so(byteptr path) { return 0; }') } + */ } diff --git a/vlib/compiler/msvc.v b/cmd/v/internal/compile/msvc.v similarity index 99% rename from vlib/compiler/msvc.v rename to cmd/v/internal/compile/msvc.v index b91cdba411..defbe2e5ad 100644 --- a/vlib/compiler/msvc.v +++ b/cmd/v/internal/compile/msvc.v @@ -1,4 +1,4 @@ -module compiler +module compile import os diff --git a/cmd/v/simple_tool.v b/cmd/v/simple_tool.v index 2fad23e1e1..9222c841c6 100644 --- a/cmd/v/simple_tool.v +++ b/cmd/v/simple_tool.v @@ -4,15 +4,23 @@ module main import ( - compiler os v.pref ) +fn set_vroot_folder(vroot_path string) { + // Preparation for the compiler module: + // VEXE env variable is needed so that compiler.vexe_path() + // can return it later to whoever needs it: + vname := if os.user_os() == 'windows' { 'v.exe' } else { 'v' } + os.setenv('VEXE', os.real_path([vroot_path, vname].join(os.path_separator)), true) +} + + fn launch_tool(verbosity pref.VerboseLevel, tool_name string) { vexe := pref.vexe_path() vroot := os.dir(vexe) - compiler.set_vroot_folder(vroot) + set_vroot_folder(vroot) tool_args := os.args[1..].join(' ') tool_exe := path_of_executable(os.real_path('$vroot/cmd/tools/$tool_name')) diff --git a/cmd/v/v.v b/cmd/v/v.v index 928139542b..cada6b4c79 100644 --- a/cmd/v/v.v +++ b/cmd/v/v.v @@ -4,7 +4,6 @@ module main import ( - compiler internal.compile internal.flag internal.help @@ -24,6 +23,11 @@ const ( 'setup-freetype'] ) + pub const ( + v_version = '0.1.26' + ) + + fn main() { prefs := flag.MainCmdPreferences{} values := flag.parse_main_cmd(os.args, parse_flags, prefs) or { @@ -32,7 +36,7 @@ fn main() { exit(1) } if prefs.verbosity.is_higher_or_equal(.level_two) { - println('V $compiler.Version $compiler.vhash()') + println('V $v_version $vhash()') } if prefs.verbosity.is_higher_or_equal(.level_three) { println('Parsed preferences: ') @@ -122,11 +126,18 @@ fn main() { } fn print_version_and_exit() { - version_hash := compiler.vhash() - println('V $compiler.Version $version_hash') + version_hash := vhash() + println('V $v_version $version_hash') exit(0) } +fn vhash() string { + mut buf := [50]byte + buf[0] = 0 + C.snprintf(charptr(buf), 50, '%s', C.V_COMMIT_HASH) + return tos_clone(buf) +} + fn invoke_help_and_exit(remaining []string) { match remaining.len { 0, 1 { diff --git a/vlib/compiler/aparser.v b/vlib/compiler/aparser.v deleted file mode 100644 index 07606d67d6..0000000000 --- a/vlib/compiler/aparser.v +++ /dev/null @@ -1,3256 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import ( - os - strings - v.pref - // compiler.x64 - time -) - -struct Parser { - file_path string // "/home/user/hello.v" - file_path_dir string // "/home/user" - file_name string // "hello.v" - file_platform string // ".v", "_windows.v", "_nix.v", "_darwin.v", "_linux.v" ... - // When p.file_pcguard != '', it contains a - // C ifdef guard clause that must be put before - // the #include directives in the parsed .v file - file_pcguard string - v &V - pref &pref.Preferences -mut: - scanner &Scanner - // Preferences shared from V struct - tokens []Token - token_idx int - prev_stuck_token_idx int - tok TokenKind - prev_tok TokenKind - prev_tok2 TokenKind // TODO remove these once the tokens are cached - lit string - cgen &CGen - // x64 &x64.Gen - table &Table - import_table ImportTable // Holds imports for just the file being parsed - pass Pass - os pref.OS - inside_const bool - expr_var Var - has_immutable_field bool - first_immutable_field Var - assigned_type string // non-empty if we are in an assignment expression - expected_type string - tmp_cnt int - builtin_mod bool - inside_if_expr bool - // inside_unwrapping_match bool - inside_return_expr bool - inside_unsafe bool - is_struct_init bool - is_var_decl bool - if_expr_cnt int - for_expr_cnt int // to detect whether `continue` can be used - ptr_cast bool - calling_c bool - cur_fn Fn - local_vars []Var // local function variables - global_vars []Var // only for "script" programs without "fn main" - var_idx int - returns bool - vroot string - is_c_struct_init bool - is_empty_c_struct_init bool // for `foo := C.Foo{}` => `Foo foo;` - is_c_fn_call bool - can_chash bool - attr string - v_script bool // "V bash", import all os functions into global space - var_decl_name string // To allow declaring the variable so that it can be used in the struct initialization - is_alloc bool // Whether current expression resulted in an allocation - is_const_literal bool // `1`, `2.0` etc, so that `u64_var == 0` works - in_dispatch bool // dispatching generic instance? - is_vgen bool - is_sql bool - is_js bool - sql_i int // $1 $2 $3 - sql_params []string // ("select * from users where id = $1", ***"100"***) - sql_types []string // int, string and so on; see sql_params - is_vh bool // parsing .vh file (for example `const (a int)` is allowed) - generic_dispatch TypeInst -pub mut: - mod string -} - -const ( - max_module_depth = 5 - reserved_types = { - 'i8': true, - 'i16': true, - 'int': true, - 'i64': true, - 'i128': true, - 'byte': true, - 'char': true, - 'u16': true, - 'u32': true, - 'u64': true, - 'u128': true, - 'f32': true, - 'f64': true, - 'rune': true, - 'byteptr': true, - 'voidptr': true - } -) - -struct ParserState { - scanner_file_path string - scanner_line_nr int - scanner_text string - scanner_pos int - scanner_line_ends []int - scanner_nlines int - cgen_lines []string - cgen_cur_line string - cgen_tmp_line string - cgen_is_tmp bool - tokens []Token - token_idx int - tok TokenKind - prev_tok TokenKind - prev_tok2 TokenKind - lit string -} - -// new parser from string. unique id specified in `id`. -// tip: use a hashing function to auto generate `id` from `text` eg. sha1.hexhash(text) -fn (v mut V) new_parser_from_string(text string) Parser { - // line comment 1 - mut p := v.new_parser(new_scanner(text)) - p.scan_tokens() // same line comment - return p - // final comment -} - -fn (v mut V) reset_cgen_file_line_parameters() { - v.cgen.line = 0 - v.cgen.file = '' - v.cgen.line_directives = v.pref.is_vlines -} - -fn (v mut V) new_parser_from_file(path string) Parser { - v.reset_cgen_file_line_parameters() - // println('new_parser("$path")') - mut path_pcguard := '' - mut path_platform := '.v' - for path_ending in ['_lin.v', '_mac.v', '_win.v', '_nix.v', '_linux.v', '_darwin.v', '_windows.v'] { - if path.ends_with(path_ending) { - if path_ending == '_mac.v' { - p := path_ending.replace('_mac.v', '_darwin.v') - println('warning: use "$p" file name instead of "$path"') - } - if path_ending == '_lin.v' { - p := path_ending.replace('_lin.v', '_linux.v') - println('warning: use "$p" file name instead of "$path"') - } - if path_ending == '_win.v' { - p := path_ending.replace('_win.v', '_windows.v') - println('warning: use "$p" file name instead of "$path"') - } - path_platform = path_ending - path_pcguard = v.platform_postfix_to_ifdefguard(path_ending) - break - } - } - if v.pref.compile_defines.len > 0 { - for cdefine in v.pref.compile_defines { - custom_path_ending := '_d_${cdefine}.v' - if path.ends_with(custom_path_ending) { - path_platform = custom_path_ending - path_pcguard = v.platform_postfix_to_ifdefguard('custom $cdefine') - break - } - } - } - mut p := v.new_parser(new_scanner_file(path)) - path_dir := os.real_path(os.dir(path)) - p = { - p | - file_path:path, - file_path_dir:path_dir, - file_name:path.all_after(os.path_separator), - file_platform:path_platform, - file_pcguard:path_pcguard, - is_vh:path.ends_with('.vh'), - v_script:path.ends_with('.vsh') - } - if p.v_script { - println('new_parser: V script') - } - if p.pref.building_v { - p.scanner.print_rel_paths_on_error = true - } - // if p.pref.generating_vh { - // Keep newlines - // p.scanner.is_vh = true - // } - p.scan_tokens() - // p.scanner.debug_tokens() - return p -} - -// creates a new parser. most likely you will want to use -// `new_parser_file` or `new_parser_string` instead. -fn (v mut V) new_parser(scanner &Scanner) Parser { - v.reset_cgen_file_line_parameters() - mut p := Parser{ - scanner: scanner - v: v - table: v.table - cur_fn: EmptyFn - cgen: v.cgen - // x64: v.x64 - - pref: v.pref - os: v.pref.os - vroot: v.pref.vroot - local_vars: [Var{}].repeat(MaxLocalVars) - import_table: new_import_table() - } - $if js { - p.is_js = true - } - if p.pref.is_repl { - p.scanner.print_line_on_error = false - p.scanner.print_colored_error = false - p.scanner.print_rel_paths_on_error = true - } - return p -} - -// __global scan_time i64 -fn (p mut Parser) scan_tokens() { - // t := time.ticks() - for { - res := p.scanner.scan() - p.tokens << Token{ - tok: res.tok - lit: res.lit - line_nr: p.scanner.line_nr - pos: p.scanner.pos - } - if res.tok == .eof { - break - } - } - // scan_time += time.ticks() - t - // println('scan tokens $p.file_name $scan_time ') -} - -fn (p mut Parser) set_current_fn(f Fn) { - p.cur_fn = f - // p.cur_fn = p.table.fns[f.name] - p.scanner.fn_name = '${f.mod}.${f.name}' -} - -fn (p mut Parser) next() { - // Generate a formatted version of this token - // (only when vfmt compile time flag is enabled, otherwise this function - // is not even generated) - p.fnext() - // - p.prev_tok2 = p.prev_tok - p.prev_tok = p.tok - p.scanner.prev_tok = p.tok - if p.token_idx >= p.tokens.len { - p.tok = .eof - p.lit = '' - return - } - res := p.tokens[p.token_idx] - p.token_idx++ - p.tok = res.tok - p.lit = res.lit - p.scanner.line_nr = res.line_nr - p.cgen.line = res.line_nr -} - -fn (p &Parser) peek() TokenKind { - if p.token_idx >= p.tokens.len - 2 { - return .eof - } - return p.tokens[p.token_idx].tok - /* - mut i := p.token_idx - for i < p.tokens.len { - tok := p.tokens[i] - if tok.tok != .mline_comment && tok.tok != .line_comment { - return tok.tok - } - i++ - } - return .eof - */ - -} - -// TODO remove dups -[inline] -fn (p &Parser) prev_token() Token { - return p.tokens[p.token_idx - 2] -} - -[inline] -fn (p &Parser) cur_tok() Token { - return p.tokens[p.token_idx - 1] -} - -[inline] -fn (p &Parser) peek_token() Token { - if p.token_idx >= p.tokens.len - 2 { - return Token{ - tok: .eof - } - } - return p.tokens[p.token_idx] -} - -fn (p &Parser) log(s string) {} - -/* - if !p.pref.is_verbose { - return - } - println(s) -*/ - - -pub fn (p &Parser) save_state() ParserState { - return ParserState{ - scanner_file_path: p.scanner.file_path - scanner_line_nr: p.scanner.line_nr - scanner_text: p.scanner.text - scanner_pos: p.scanner.pos - scanner_line_ends: p.scanner.line_ends - scanner_nlines: p.scanner.nlines - cgen_lines: p.cgen.lines - cgen_cur_line: p.cgen.cur_line - cgen_tmp_line: p.cgen.tmp_line - cgen_is_tmp: p.cgen.is_tmp - tokens: p.tokens - token_idx: p.token_idx - tok: p.tok - prev_tok: p.prev_tok - prev_tok2: p.prev_tok2 - lit: p.lit - } -} - -pub fn (p mut Parser) restore_state(state ParserState, scanner bool, cgen bool) { - if scanner { - p.scanner.file_path = state.scanner_file_path - p.scanner.line_nr = state.scanner_line_nr - p.scanner.text = state.scanner_text - p.scanner.pos = state.scanner_pos - p.scanner.line_ends = state.scanner_line_ends - p.scanner.nlines = state.scanner_nlines - } - if cgen { - p.cgen.lines = state.cgen_lines - p.cgen.cur_line = state.cgen_cur_line - p.cgen.tmp_line = state.cgen_tmp_line - p.cgen.is_tmp = state.cgen_is_tmp - } - p.tokens = state.tokens - p.token_idx = state.token_idx - p.tok = state.tok - p.prev_tok = state.prev_tok - p.prev_tok2 = state.prev_tok2 - p.lit = state.lit -} - -fn (p mut Parser) clear_state(scanner bool, cgen bool) { - if scanner { - p.scanner.line_nr = 0 - p.scanner.text = '' - p.scanner.pos = 0 - p.scanner.line_ends = [] - p.scanner.nlines = 0 - } - if cgen { - p.cgen.lines = [] - p.cgen.cur_line = '' - p.cgen.tmp_line = '' - p.cgen.is_tmp = false - } - p.tokens = [] - p.token_idx = 0 - p.lit = '' -} - -pub fn (p mut Parser) add_text(text string) { - if p.tokens.len > 1 && p.tokens[p.tokens.len - 1].tok == .eof { - p.tokens.delete(p.tokens.len - 1) - } - p.scanner.text = p.scanner.text + '\n' + text - p.scan_tokens() -} - -fn (p mut Parser) statements_from_text(text string, rcbr bool, fpath string) { - saved_state := p.save_state() - p.clear_state(true, false) - if fpath != '' { - p.scanner.file_path = fpath - } - p.add_text(text) - p.next() - if rcbr { - p.statements() - } - else { - p.statements_no_rcbr() - } - p.restore_state(saved_state, true, false) -} - -fn (p mut Parser) parse(pass Pass) { - p.cgen.line = 0 - p.cgen.file = cescaped_path(os.real_path(p.file_path)) - // /////////////////////////////////// - p.pass = pass - p.token_idx = 0 - p.next() - // 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.pref.is_script || p.pref.is_test { - // User may still specify `module main` - if p.tok == .key_module { - p.next() - p.fspace() - p.mod = p.check_name() - } - else { - p.mod = 'main' - } - } - else { - p.check(.key_module) - p.fspace() - p.mod = p.check_name() - } - // - p.fgen_nl() - p.cgen.nogen = false - if p.pref.build_mode == .build_module && p.mod != p.v.pref.mod { - // println('skipping $p.mod (v.mod = $p.v.mod)') - p.cgen.nogen = true - // defer { p.cgen.nogen = false } - } - p.fgen_nl() - p.builtin_mod = p.mod == 'builtin' - p.can_chash = p.mod in ['parser', 'gg2', 'ui', 'uiold', 'darwin', 'clipboard', 'webview', 'gen'] // TODO tmp remove - // Import pass - the first and the smallest pass that only analyzes imports - // if we are a building module get the full module name from v.mod - fq_mod := if p.pref.build_mode == .build_module && p.v.pref.mod.ends_with(p.mod) { p.v.pref.mod } - // fully qualify the module name, eg base64 to encoding.base64 - else { p.table.qualify_module(p.mod, p.file_path) } - p.table.register_module(fq_mod) - p.mod = fq_mod - if p.pass == .imports { - for p.tok == .key_import && p.peek() != .key_const { - p.imports() - } - if 'builtin' in p.table.imports { - p.error('module `builtin` cannot be imported') - } - return - } - parsing_start_ticks := time.ticks() - compile_cycles_stuck_mask := u64(0x1FFFFFFF) // 2^29-1 cycles - mut parsing_cycle := u64(1) - p.prev_stuck_token_idx = p.token_idx - // Go through every top level token or throw a compilation error if a non-top level token is met - for { - parsing_cycle++ - if compile_cycles_stuck_mask == (parsing_cycle & compile_cycles_stuck_mask) { - p.check_if_parser_is_stuck(parsing_cycle, parsing_start_ticks) - } - match p.tok { - .key_import { - p.imports() - } - .key_enum { - next := p.peek() - if next == .name { - p.enum_decl(false) - } - else if next == .lcbr && p.pref.translated { - // enum without a name, only allowed in code, - // translated from C. it's a very bad practice - // in C as well, but is used unfortunately - // (for example, by DOOM). such fields are - // basically int consts - p.enum_decl(true) - } - else { - p.error('Nameless enums are not allowed.') - } - } - .key_pub { - next := p.peek() - match next { - .key_fn { - p.fn_decl() - } - .key_const { - p.const_decl() - } - .key_struct, .key_union, .key_interface { - p.struct_decl([]) - } - .key_enum { - p.enum_decl(false) - } - .key_type { - p.type_decl() - } - else { - p.error('wrong pub keyword usage') - } - } - } - .key_fn { - p.fn_decl() - } - .key_type { - p.type_decl() - } - .lsbr { - // `[` can only mean an [attribute] before a function - // or a struct definition - p.attribute() - } - .key_struct, .key_interface, .key_union, .lsbr { - p.struct_decl([]) - } - .key_const { - p.const_decl() - } - .hash { - // insert C code (only for ui module) - // # puts("hello"); - p.chash() - } - .dollar { - // $if, $else - p.comp_time() - } - .key_global { - if !p.pref.translated && !p.pref.is_live && !p.builtin_mod && !p.pref.building_v && p.mod != 'ui' && p.mod != 'gg2' && p.mod != 'uiold' && !os.getwd().contains('/volt') && !p.pref.enable_globals { - p.error('use `v --enable-globals ...` to enable globals') - } - p.next() - p.fspace() - name := p.check_name() - p.fspace() - typ := p.get_type() - p.register_global(name, typ) - // p.genln(p.table.cgen_name_type_pair(name, typ)) - mut g := p.table.cgen_name_type_pair(name, typ) - if p.tok == .assign { - p.next() - g += ' = ' - _,expr := p.tmp_expr() - g += expr - } - // p.genln('; // global') - g += '; // global' - if !p.cgen.nogen { - p.cgen.consts << g - } - p.fgen_nl() - if p.tok != .key_global { - // An extra empty line to separate a block of globals - p.fgen_nl() - } - } - .eof { - // p.log('end of parse()') - // TODO: check why this was added? everything seems to work - // without it, and it's already happening in fn_decl - // if p.pref.is_script && !p.pref.is_test { - // p.set_current_fn( MainFn ) - // p.check_unused_variables() - // } - if !p.first_pass() && !p.pref.is_repl { - p.check_unused_imports() - } - p.gen_fmt() // not generated unless `-d vfmt` is provided - return - } - else { - // no `fn main`, add this "global" statement to cgen.fn_main - if p.pref.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.cur_fn.name == '' { - p.set_current_fn(MainFn) - if p.pref.is_repl { - if p.first_pass() { - return - } - p.clear_vars() - } - } - start := p.cgen.lines.len - p.statement(true) - // if start > 0 && p.cgen.lines[start - 1] != '' && - // p.cgen.fn_main != '' { - // start-- - // } - p.genln('') - end := p.cgen.lines.len - lines := p.cgen.lines[start..end] - // println('adding "' + lines.join('\n') + '"\n') - // mut line := p.cgen.fn_main + lines.join('\n') - // line = line.trim_space() - p.cgen.fn_main += lines.join('\n') - p.cgen.resetln('') - for i := start; i < end; i++ { - p.cgen.lines[i] = '' - } - p.fgen_nl() - } - else { - p.error('unexpected token `${p.strtok()}`') - } - } - } - } -} - -fn (p mut Parser) imports() { - p.check(.key_import) - // `import ()` - if p.tok == .lpar { - p.fspace() - p.check(.lpar) - p.fmt_inc() - p.fgen_nl() - for p.tok != .rpar && p.tok != .eof { - p.import_statement() - p.fgen_nl() - } - p.fmt_dec() - p.check(.rpar) - p.fgenln('\n') - return - } - // `import foo` - p.import_statement() - p.fgen_nl() - if p.tok != .key_import { - p.fgen_nl() - } -} - -fn (p mut Parser) import_statement() { - p.fspace() - if p.tok != .name { - p.error('bad import format') - } - if p.peek() == .number { - p.error('bad import format: module/submodule names cannot begin with a number') - } - import_tok_idx := p.token_idx - 1 - mut mod := p.check_name().trim_space() - mut mod_alias := mod - // submodule support - mut depth := 1 - for p.tok == .dot { - p.check(.dot) - submodule := p.check_name() - mod_alias = submodule - mod += '.' + submodule - depth++ - if depth > max_module_depth { - p.error('module depth of $max_module_depth exceeded: $mod') - } - } - // aliasing (import encoding.base64 as b64) - if p.tok == .key_as && p.peek() == .name { - p.fspace() - p.check(.key_as) - p.fspace() - mod_alias = p.check_name() - } - // add import to file scope import table - p.register_import_alias(mod_alias, mod, import_tok_idx) - // Make sure there are no duplicate imports - if mod in p.table.imports { - return - } - // p.log('adding import $mod') - p.table.imports << mod - p.table.register_module(mod) -} - -fn (p mut Parser) const_decl() { - // println('const decl $p.file_path') - is_pub := p.tok == .key_pub - if is_pub { - p.next() - p.fspace() - } - p.inside_const = true - p.check(.key_const) - p.fspace() - p.check(.lpar) - p.fgen_nl() - p.fmt_inc() - for p.tok == .name { - if p.lit == '_' && p.peek() == .assign && !p.cgen.nogen { - p.gen_blank_identifier_assign() - // if !p.cgen.nogen { - p.cgen.consts_init << p.cgen.cur_line.trim_space() - p.cgen.resetln('') - // } - continue - } - var_token_idx := p.cur_tok_index() - mut name := p.check_name() // `Age = 20` - // if !p.pref.building_v && p.mod != 'os' && contains_capital(name) { - // p.warn('const names cannot contain uppercase letters, use snake_case instead') - // } - name = p.prepend_mod(name) - mut typ := '' - if p.first_pass() && p.table.known_const(name) { - p.error_with_token_index('redefinition of `$name`', var_token_idx) - } - if p.is_vh { - // println('CONST VH $p.file_path') - // .vh files may not have const values, just types: `const (a int)` - if p.tok == .assign { - p.next() - // Otherwise parse the expression to get its type, - // but don't generate it. Const's value is generated - // in "module.o". - p.cgen.nogen = true - typ = p.expression() - p.cgen.nogen = false - } - else { - typ = p.get_type() - } - p.table.register_const(name, typ, p.mod, is_pub) - p.cgen.consts << ('extern ' + p.table.cgen_name_type_pair(name, typ)) + ';' - continue // Don't generate C code when building a .vh file - } - else { - p.check_space(.assign) - typ = p.expression() - } - if p.first_pass() { - p.table.register_const(name, typ, p.mod, is_pub) - } - // Check to see if this constant exists, and is unresolved. If so, try and get the type again: - if my_const := p.v.table.find_const(name) { - if my_const.typ == 'unresolved' { - for i, v in p.v.table.consts { - if v.name == name { - p.v.table.consts[i].typ = typ - break - } - } - } - } - if p.pass == .main && p.cgen.nogen && p.pref.build_mode == .build_module { - // We are building module `ui`, but are parsing `gx` right now - // (because of nogen). We need to import gx constants with `extern`. - // println('extern const mod=$p.mod name=$name') - p.cgen.consts << ('extern ' + p.table.cgen_name_type_pair(name, typ)) + ';' - } - if p.pass == .main && !p.cgen.nogen { - // 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 - // Do not do this when building a module, otherwise the consts - // will not be accessible. - if p.pref.build_mode != .build_module && is_compile_time_const(p.cgen.cur_line) { - mut const_val := p.cgen.cur_line - // fix `warning: integer literal is too large to be represented in a signed integer type` - if typ == 'u64' { - const_val = 'UINT64_C($const_val)' - } - p.cgen.const_defines << '#define $name $const_val' - p.cgen.resetln('') - p.fgen_nl() - continue - } - if typ.starts_with('[') { - p.cgen.consts << p.table.cgen_name_type_pair(name, typ) + ' = $p.cgen.cur_line;' - } - else { - p.cgen.consts << p.table.cgen_name_type_pair(name, typ) + ';' - // println('adding to init "$name"') - p.cgen.consts_init << '$name = $p.cgen.cur_line;' - } - p.cgen.resetln('') - } - p.fgen_nl() - } - p.fmt_dec() - p.check(.rpar) - p.inside_const = false - p.fgen_nl() - p.fgen_nl() -} - -// `type myint int` -// `type onclickfn fn(voidptr) int` -fn (p mut Parser) type_decl() { - is_pub := p.tok == .key_pub - if is_pub { - p.next() - p.fspace() - } - p.check(.key_type) - p.fspace() - mut name := p.check_name() - p.fspace() - // V used to have 'type Foo struct', many Go users might use this syntax - if p.tok == .key_struct { - p.error('use `struct $name {` instead of `type $name struct {`') - } - is_sum := p.tok == .assign - if is_sum { - p.next() - p.fspace() - } - mut parent := Type{} - if !p.builtin_mod && p.mod != 'main' { - name = p.prepend_mod(name) - } - // Sum type - // is_sum := p.tok == .pipe - if is_sum { - // Register the first child (name we already parsed) - /* - p.table.register_type(Type{ - parent: name - name: parent.name // yeah it's not a parent here - mod: p.mod - is_public: is_pub - }) - */ - if p.pass == .main { - p.cgen.consts << '//// SUMTYPE: ${p.mod} | parent: ${name} | name: ${parent.name}' - } - // Register the rest of them - mut idx := 0 - mut done := false - mut ctype_names := []string - mut sum_variants := []string - for { - // p.tok == .pipe { - idx++ - // p.next() - child_type_name := p.check_name() - // println('$idx $child_type_name') - if p.tok != .pipe { - done = true - } - if p.pass == .main { - // Update the type's parent - // println('child=$child_type_name parent=$name') - t := p.find_type(child_type_name) - if t.name == '' { - p.error('unknown type `$child_type_name`') - } - p.cgen.consts << '#define SumType_${name}_$child_type_name $idx // DEF2' - ctype_names << child_type_name - sum_variants << if p.mod in ['builtin', 'main'] || child_type_name in builtin_types { child_type_name } else { p.prepend_mod(child_type_name) } - } - if done { - break - } - p.fspace() - p.check(.pipe) - p.fspace() - if p.tokens[p.token_idx - 2].line_nr < p.tokens[p.token_idx - 1].line_nr { - p.fgenln('\t') - // p.fgen_nl() - } - } - p.table.sum_types[name] = sum_variants - // Register the actual sum type - // println('registering sum $name') - p.table.register_type(Type{ - name: name - mod: p.mod - cat: .alias - is_public: is_pub - ctype_names: ctype_names - }) - if p.pass == .main { - p.cgen.consts << 'const char * __SumTypeNames__${name}[] = {' - p.cgen.consts << ' "$name",' - for ctype_name in ctype_names { - p.cgen.consts << ' "$ctype_name",' - } - p.cgen.consts << '};' - } - p.gen_typedef('typedef struct { -void* obj; -int typ; -} $name; -') - } - else { - parent = p.get_type2() - } - nt_pair := p.table.cgen_name_type_pair(name, parent.name) - // TODO dirty C typedef hacks for DOOM - // Unknown type probably means it's a struct, and it's used before the struct is defined, - // so specify "struct" - _struct := if parent.cat != .array && parent.cat != .func && !p.table.known_type(parent.name) { 'struct' } else { '' } - if !is_sum { - p.gen_typedef('typedef $_struct $nt_pair; //type alias name="$name" parent=`$parent.name`') - p.table.register_type(Type{ - name: name - parent: parent.name - mod: p.mod - cat: .alias - is_public: is_pub - }) - } - // if p.tok != .key_type { - p.fgen_nl() - p.fgen_nl() - // } -} -// current token is `(` -fn (p mut Parser) interface_method(field_name, receiver string) &Fn { - mut method := &Fn{ - name: field_name - is_interface: true - is_method: true - receiver_typ: receiver - } - // p.log('is interface. field=$field_name run=$p.pass') - p.fn_args(mut method) - prev_tok := p.prev_token() - cur_tok := p.cur_tok() - // No type on the same line, this method doesn't return a type, process next - if prev_tok.line_nr != cur_tok.line_nr { - method.typ = 'void' - } - else { - method.typ = p.get_type() // method return type - // p.fspace() - p.fgen_nl() - } - return method -} - -fn key_to_type_cat(tok TokenKind) TypeCategory { - match tok { - .key_interface { - return .interface_ - } - .key_struct { - return .struct_ - } - .key_union { - return .union_ - } - else {} - } - verror('Unknown token: $tok') - return .builtin -} - -// check_name checks for a name token and returns its literal -fn (p mut Parser) check_name() string { - name := p.lit - p.check(.name) - return name -} - -fn (p mut Parser) check_string() string { - s := p.lit - p.check(.string) - return s -} - -fn (p mut Parser) check_not_reserved() { - if reserved_types[p.lit] { - p.error("`$p.lit` can\'t be used as name") - } -} - -fn (p &Parser) strtok() string { - if p.tok == .name { - return p.lit - } - if p.tok == .number { - return p.lit - } - if p.tok == .chartoken { - if p.lit == '`' { - return '`\\$p.lit`' - } - return '`$p.lit`' - } - if p.tok == .string{ - if p.lit.contains("'") && !p.lit.contains('"') { - return '"$p.lit"' - } - else { - return "'$p.lit'" - } - } - if p.tok == .hash { - return '#' + p.lit - } - res := p.tok.str() - if res == '' { - n := int(p.tok) - return n.str() - } - return res -} - -// same as check(), but adds a space to the formatter output -// TODO bad name -fn (p mut Parser) check_space(expected TokenKind) { - p.fspace() - p.check(expected) - p.fspace() -} - -fn (p mut Parser) check(expected TokenKind) { - if p.tok != expected { - // println('check()') - s := 'syntax error: unexpected `${p.strtok()}`, expecting `${expected.str()}`' - p.next() - println('next token = `${p.strtok()}`') - if p.pref.is_debug { - print_backtrace() - } - p.error(s) - } - /* - if expected == .rcbr { - p.fmt_dec() - } - p.fgen(p.strtok()) - // vfmt: increase indentation on `{` unless it's `{}` - if expected == .lcbr { //&& p.scanner.pos + 1 < p.scanner.text.len && p.scanner.text[p.scanner.pos + 1] != `}` { - p.fgen_nl() - p.fmt_inc() - } - */ - - p.next() - // if p.scanner.line_comment != '' { - // p.fgenln('// ! "$p.scanner.line_comment"') - // p.scanner.line_comment = '' - // } -} - -[inline] -fn (p &Parser) first_pass() bool { - return p.pass == .decl -} - -// TODO return Type instead of string? -fn (p mut Parser) get_type() string { - mut mul := false - mut nr_muls := 0 - mut typ := '' - // fn type - if p.tok == .key_fn { - mut f := Fn{ - name: '_' - mod: p.mod - } - p.next() - line_nr := p.scanner.line_nr - p.fn_args(mut f) - // Same line, it's a return type - if p.scanner.line_nr == line_nr { - if p.tok in [.name, .mul, .amp, .lsbr, .question, .lpar] { - f.typ = p.get_type() - } - else { - f.typ = 'void' - } - // println('fn return typ=$f.typ') - } - else { - f.typ = 'void' - } - // Register anon fn type - fn_typ := Type{ - name: f.typ_str() // 'fn (int, int) string' - - mod: p.mod - func: f - } - p.table.register_type(fn_typ) - return f.typ_str() - } - is_question := p.tok == .question - if is_question { - p.check(.question) - } - // multiple returns - if p.tok == .lpar { - // p.warn('`()` are no longer necessary in multiple returns' + - // '\nuse `fn foo() int, int {` instead of `fn foo() (int, int) {`') - // if p.inside_tuple {p.error('unexpected (')} - // p.inside_tuple = true - p.check(.lpar) - mut types := []string - for { - types << p.get_type() - if p.tok != .comma { - break - } - p.check(.comma) - } - p.check(.rpar) - // p.inside_tuple = false - typ = p.register_multi_return_stuct(types) - if is_question { - typ = stringify_pointer(typ) - typ = 'Option_$typ' - p.table.register_type_with_parent(typ, 'Option') - } - return typ - } - // arrays ([]int) - mut arr_level := 0 - for p.tok == .lsbr { - p.check(.lsbr) - // [10]int - if p.tok == .number { - typ += '[$p.lit]' - p.next() - } - else { - arr_level++ - } - p.check(.rsbr) - } - // map[string]int - if !p.builtin_mod && p.tok == .name && p.lit == 'map' { - p.next() - p.check(.lsbr) - key_type := p.check_name() - if key_type != 'string' { - p.error('maps only support string keys for now') - } - p.check(.rsbr) - val_type := p.get_type() // p.check_name() - typ = 'map_${stringify_pointer(val_type)}' - p.register_map(typ) - return typ - } - // ptr/ref - mut warn := false - for p.tok == .mul { - if p.first_pass() { - warn = true - } - mul = true - nr_muls++ - p.check(.mul) - } - if p.tok == .amp { - mul = true - nr_muls++ - p.check(.amp) - } - // generic type check - ti := p.generic_dispatch.inst - if p.lit in ti.keys() { - typ += ti[p.lit] - } - else { - typ += p.lit - } - // C.Struct import - if p.lit == 'C' && p.peek() == .dot { - p.next() - p.check(.dot) - typ = p.lit - } - else { - if warn && p.mod != 'ui' { - p.warn('use `&Foo` instead of `*Foo`') - } - // Module specified? (e.g. gx.Image) - if p.peek() == .dot { - // try resolve full submodule - if !p.builtin_mod && p.import_table.known_alias(typ) { - mod := p.import_table.resolve_alias(typ) - if mod.contains('.') { - typ = mod_gen_name(mod) - } - } - p.next() - p.check(.dot) - typ += '__$p.lit' - } - mut t := p.table.find_type(typ) - // "typ" not found? try "mod__typ" - if t.name == '' && !p.builtin_mod { - // && !p.first_pass() { - if !typ.contains('array_') && p.mod != 'main' && !typ.contains('__') && !typ.starts_with('[') { - typ = p.prepend_mod(typ) - } - t = p.table.find_type(typ) - 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 { - // println(q.name) - // } - mut t_suggest,tc_suggest := p.table.find_misspelled_type(typ, p, 0.50) - if t_suggest.len > 0 { - t_suggest = '. did you mean: ($tc_suggest) `$t_suggest`' - } - econtext := if p.pref.is_debug { '(' + '/Users/alex/code/v/vlib/compiler/aparser.v' + ':' + '1236' + ')' } else { '' } - p.error('unknown type `$typ`$t_suggest $econtext') - } - } - else if !t.is_public && t.mod != p.mod && !p.is_vgen && t.name != '' && !p.first_pass() { - p.error('type `$t.name` is private') - } - } - // generic struct init - if p.peek() == .lt { - p.next() - p.check(.lt) - typ = '${typ}_T' - for { - type_param := p.check_name() - if type_param in p.generic_dispatch.inst { - typ = '${typ}_' + p.generic_dispatch.inst[type_param] - } - if p.tok != .comma { - break - } - p.check(.comma) - } - p.check(.gt) - return typ - } - if typ == 'void' { - p.error('unknown type `$typ`') - } - if mul { - typ += strings.repeat(`*`, nr_muls) - } - // Register an []array type - if arr_level > 0 { - // p.log('ARR TYPE="$typ" run=$p.pass') - // We come across "[]User" etc ? - for i := 0; i < arr_level; i++ { - typ = 'array_${stringify_pointer(typ)}' - } - p.register_array(typ) - } - p.next() - if is_question { - typ = 'Option_${stringify_pointer(typ)}' - p.table.register_type_with_parent(typ, 'Option') - } - // Because the code uses * to see if it's a pointer - if typ == 'byteptr' { - return 'byte*' - } - if typ == 'voidptr' { - // if !p.builtin_mod && p.mod != 'os' && p.mod != 'gx' && p.mod != 'gg' && !p.pref.translated { - // p.error('voidptr can only be used in unsafe code') - // } - return 'void*' - } - /* - TODO this is not needed? - if typ.last_index('__') > typ.index('__') { - p.error('2 __ in gettype(): typ="$typ"') - } - */ - - return typ -} - -fn (p &Parser) print_tok() { - if p.tok == .name { - println(p.lit) - return - } - if p.tok == .string{ - println('"$p.lit"') - return - } - println(p.tok.str()) -} - -// statements() returns the type of the last statement -fn (p mut Parser) statements() string { - // p.log('statements()') - typ := p.statements_no_rcbr() - if !p.inside_if_expr { - p.genln('}') - } - // if p.fileis('if_expr') { - // println('statements() ret=$typ line=$p.scanner.line_nr') - // } - return typ -} - -fn (p mut Parser) statements_no_rcbr() string { - p.open_scope() - // if !p.inside_if_expr { - // p.genln('') - // } - mut i := 0 - mut last_st_typ := '' - for p.tok != .rcbr && p.tok != .eof { - // println('stm: '+p.tok.str()+', next: '+p.peek().str()) - last_st_typ = p.statement(true) - // println('last st typ=$last_st_typ') - if !p.inside_if_expr { - // p.genln('')// // end st tok= ${p.strtok()}') - // p.fgenln('// ST') - p.fgen_nl() - } - i++ - if i > 50000 { - p.cgen.save() - p.error('more than 50 000 statements in function `$p.cur_fn.name`') - } - } - // p.next() - if p.inside_if_expr { - p.fspace() - } - p.check(.rcbr) - // p.fmt_dec() - p.close_scope() - return last_st_typ -} - -fn (p mut Parser) close_scope() { - // println('close_scope level=$f.scope_level var_idx=$f.var_idx') - // Move back `var_idx` (pointer to the end of the array) till we reach - // the previous scope level. This effectivly deletes (closes) current - // scope. - mut i := p.var_idx - 1 - for ; i >= 0; i-- { - v := p.local_vars[i] - if p.pref.autofree && (v.is_alloc || (v.is_arg && v.typ == 'string')) { - // && !p.pref.is_test { - p.free_var(v) - } - // if p.fileis('mem.v') { - // println(v.name + ' $v.is_arg scope=$v.scope_level cur=$p.cur_fn.scope_level')} - if v.scope_level != p.cur_fn.scope_level { - // && !v.is_arg { - break - } - } - if p.cur_fn.defer_text.last() != '' { - p.genln(p.cur_fn.defer_text.last()) - // p.cur_fn.defer_text[f] = '' - } - p.cur_fn.scope_level-- - p.cur_fn.defer_text = p.cur_fn.defer_text[..p.cur_fn.scope_level + 1] - p.var_idx = i + 1 - // println('close_scope new var_idx=$f.var_idx\n') -} - -fn (p mut Parser) free_var(v Var) { - // Clean up memory, only do this if -autofree was passed for now - // if p.fileis('mem.v') {println('free_var() $v.name')} - // println(p.cur_fn.name) - if p.cur_fn.name in ['add', 'clone', 'free'] { - return - } - mut free_fn := 'free' - if v.typ.starts_with('array_') { - free_fn = 'v_array_free' - } - else if v.typ == 'string' { - free_fn = 'v_string_free' - // if p.fileis('str.v') { - // println('freeing str $v.name') - // } - // continue - } - else if v.ptr || v.typ.ends_with('*') { - free_fn = 'v_ptr_free' - // continue - } - else { - return - } - if p.returns { - // Don't free a variable that's being returned - if !v.is_returned && v.typ != 'FILE*' { - // !v.is_c { - // p.cgen.cur_line = '/* free */' + p.cgen.cur_line - // p.cgen.set_placeholder(0, '/*free2*/') - prev_line := p.cgen.lines[p.cgen.lines.len - 1] - free := '$free_fn ($v.name); /* :) close_scope free $v.typ */' - p.cgen.lines[p.cgen.lines.len - 1] = free + '\n' + prev_line - // '$free_fn ($v.name); /* :) close_scope free $v.typ */\n' + prev_line - } - } - else if p.mod != 'strings' { - // && p.mod != 'builtin' { - /* - prev_line := p.cgen.lines[p.cgen.lines.len-1] - free := '$free_fn ($v.name); /* :) close_scope free $v.typ */' - p.cgen.lines[p.cgen.lines.len-1] = free + '\n' + prev_line - */ - // if p.fileis('mem.v') {println(v.name)} - p.genln('$free_fn ($v.name); // close_scope free') - } -} - -fn (p mut Parser) genln(s string) { - p.cgen.genln(s) -} - -fn (p mut Parser) gen(s string) { - p.cgen.gen(s) -} - -// Generate V header from V source -fn (p mut Parser) statement(add_semi bool) string { - p.expected_type = '' - if p.returns { - p.warn_or_error('unreachable code') - } - // if !p.in_dispatch { - p.cgen.is_tmp = false - // } - tok := p.tok - mut q := '' - match tok { - .name { - next := p.peek() - // if p.pref.is_verbose { - // println(next.str()) - // } - // goto_label: - if p.peek() == .colon { - p.fmt_dec() - label := p.check_name() - p.fmt_inc() - p.genln(label + ': ;') - p.check(.colon) - return '' - } - // `a := 777` - else if p.peek() == .decl_assign || p.peek() == .comma { - p.check_not_reserved() - // p.log('var decl') - p.var_decl() - } - // `_ = 777` - else if p.lit == '_' && p.peek() == .assign { - p.gen_blank_identifier_assign() - } - else { - // panic and exit count as returns since they stop the function - is_panic := p.lit == 'panic' || p.lit == 'exit' - if is_panic { - p.returns = true - } - // `a + 3`, `a(7)`, or just `a` - q = p.bool_expression() - // Fix "control reaches end of non-void function" error - if is_panic && p.cur_fn.typ == 'bool' { - p.genln(';\nreturn false;') - } - } - } - .key_goto { - p.check(.key_goto) - p.fspace() - label := p.check_name() - p.genln('goto $label;') - return '' - } - .key_defer { - p.defer_st() - return '' - } - .hash { - p.chash() - return '' - } - .key_unsafe { - p.next() - p.inside_unsafe = true - p.check(.lcbr) - p.genln('{') - p.statements() - p.inside_unsafe = false - // p.check(.rcbr) - } - .dollar { - p.comp_time() - } - .key_if { - p.if_statement(false, 0) - } - .key_for { - p.for_st() - } - .key_switch { - p.switch_statement() - } - .key_match { - p.match_statement(false) - } - .key_mut, .key_static { - p.var_decl() - } - .key_return { - p.return_st() - } - .lcbr { - // {} block - // Do not allow {} block to start on the same line - // to avoid e.g. `foo() {` instead of `if foo() {` - if p.prev_token().line_nr == p.scanner.line_nr { - p.genln('') - p.error('{} block has to start on a new line') - } - p.check(.lcbr) - if p.tok == .rcbr { - p.error('empty statements block') - } - p.genln('{') - p.statements() - return '' - } - .key_continue { - if p.for_expr_cnt == 0 { - p.error('`continue` statement outside `for`') - } - p.genln('continue') - p.check(.key_continue) - } - .key_break { - if p.for_expr_cnt == 0 { - p.error('`break` statement outside `for`') - } - p.genln('break') - p.check(.key_break) - } - .key_go { - p.go_statement() - } - .key_assert { - p.assert_statement() - } - .key_asm { - p.inline_asm() - } - else { - // An expression as a statement - typ := p.expression() - if p.inside_if_expr {} - else { - p.genln('; ') - } - return typ - } - } - // ? : uses , as statement separators - if p.inside_if_expr && p.tok != .rcbr { - p.gen(', ') - } - if add_semi && !p.inside_if_expr { - p.genln(';') - } - return q - // p.cgen.end_statement() -} -// is_map: are we in map assignment? (m[key] = val) if yes, dont generate '=' -// this can be `user = ...` or `user.field = ...`, in both cases `v` is `user` -fn (p mut Parser) assign_statement(v Var, ph int, is_map bool) { - errtok := p.cur_tok_index() - is_vid := p.fileis('vid') // TODO remove - tok := p.tok - // if !v.is_mut && !v.is_arg && !p.pref.translated && !v.is_global{ - if !v.is_mut && !p.pref.translated && !v.is_global && !is_vid { - if v.is_arg { - if p.cur_fn.args.len > 0 && p.cur_fn.args[0].name == v.name { - println('make the receiver `$v.name` mutable: -fn ($v.name mut $v.typ) ${p.cur_fn.name}(...) { -') - } - } - p.error('`$v.name` is immutable') - } - if !v.is_changed { - p.mark_var_changed(v) - } - is_str := p.expected_type == 'string' - is_ustr := p.expected_type == 'ustring' - match tok { - .assign { - if !is_map && !p.is_empty_c_struct_init { - p.gen(' = ') - } - } - .plus_assign { - if is_str && !p.is_js { - expr := p.cgen.cur_line - p.gen('= string_add($expr, ') - } - else if is_ustr { - p.gen('= ustring_add($v.name, ') - } - else { - next := p.peek_token() - if next.tok == .number && next.lit == '1' { - p.error('use `++` instead of `+= 1`') - } - p.gen(' += ') - } - } - .minus_assign { - next := p.peek_token() - if next.tok == .number && next.lit == '1' { - p.error('use `--` instead of `-= 1`') - } - p.gen(' -= ') - } - else { - p.gen(' ' + p.tok.str() + ' ') - } - } - p.fspace() - p.next() - p.fspace() - pos := p.cgen.cur_line.len - expr_tok := p.cur_tok_index() - p.is_var_decl = true - expr_type := p.bool_expression() - p.is_var_decl = false - // if p.expected_type.starts_with('array_') { - // p.warn('expecting array got $expr_type') - // } - if expr_type == 'void' { - _,fn_name := p.is_expr_fn_call(expr_tok + 1) - p.error_with_token_index('${fn_name}() $err_used_as_value', expr_tok) - } - // Allow `num = 4` where `num` is an `?int` - if p.assigned_type.starts_with('Option_') && expr_type == parse_pointer(p.assigned_type['Option_'.len..]) { - expr := p.cgen.cur_line[pos..] - left := p.cgen.cur_line[..pos] - typ := parse_pointer(expr_type.replace('Option_', '')) - p.cgen.resetln(left + 'opt_ok(($typ[]){ $expr }, sizeof($typ))') - } - else if expr_type.starts_with('Option_') && p.assigned_type == parse_pointer(expr_type['Option_'.len..]) && p.tok == .key_orelse { - line := p.cgen.cur_line - vname := line[..pos].replace('=', '') // TODO cgen line hack - if idx := line.index('=') { - p.cgen.resetln(line.replace(line[..idx + 1], '')) - p.gen_handle_option_or_else(expr_type, vname, ph) - } - } - else if expr_type[0] == `[` { - // assignment to a fixed_array `mut a:=[3]int a=[1,2,3]!!` - expr := p.cgen.cur_line[pos..].all_after('{').all_before('}') // TODO cgen line hack - left := p.cgen.cur_line[..pos].all_before('=') - cline_pos := p.cgen.cur_line[pos..] - etype := cline_pos.all_before(' {') - if p.assigned_type != p.expected_type { - p.error_with_token_index('incompatible types: $p.assigned_type != $p.expected_type', errtok) - } - p.cgen.resetln('memcpy( (& $left), ($etype{$expr}), sizeof( $left ) );') - } - // check type for +=, -=, *=, /=. - else if tok in [.plus_assign, .minus_assign, .mult_assign, .div_assign] { - // special 1. ptrs with += or -= are acceptable. - if !(tok in [.plus_assign, .minus_assign] && (is_integer_type(p.assigned_type) || is_pointer_type(p.assigned_type)) && (is_integer_type(expr_type) || is_pointer_type(expr_type))) { - // special 2. `str += str` is acceptable - if !(tok == .plus_assign && p.assigned_type == expr_type && expr_type == 'string') { - if !is_number_type(p.assigned_type) { - p.error_with_token_index('cannot use assignment operator ${tok.str()} on non-numeric type `$p.assigned_type`', errtok) - } - if !is_number_type(expr_type) { - p.error_with_token_index('cannot use non-numeric type `$expr_type` as assignment operator ${tok.str()} argument', errtok) - } - } - } - } - // check type for <<= >>= %= ^= &= |= - else if tok in [.left_shift_assign, .righ_shift_assign, .mod_assign, .xor_assign, .and_assign, .or_assign] { - if !is_integer_type(p.assigned_type) { - p.error_with_token_index('cannot use ${tok.str()} assignment operator on non-integer type `$p.assigned_type`', errtok) - } - if !is_integer_type(expr_type) { - p.error_with_token_index('cannot use non-integer type `$expr_type` as ${tok.str()} argument', errtok) - } - } - else if !p.builtin_mod && !p.check_types_no_throw(expr_type, p.assigned_type) { - t := p.table.find_type(p.assigned_type) - if t.cat == .enum_ && t.is_flag { - p.error_with_token_index(err_modify_bitfield, errtok) - } - p.error_with_token_index('cannot use type `$expr_type` as type `$p.assigned_type` in assignment', errtok) - } - if (is_str || is_ustr) && tok == .plus_assign && !p.is_js { - p.gen(')') - } - // p.assigned_var = '' - p.assigned_type = '' - if !v.is_used { - p.mark_var_used(v) - } -} - -fn (p mut Parser) var_decl() { - p.is_alloc = false - is_mut := p.tok == .key_mut || p.prev_tok == .key_for - is_static := p.tok == .key_static - if p.tok == .key_mut { - p.check(.key_mut) - p.fspace() - } - if p.tok == .key_static { - p.check(.key_static) - p.fspace() - } - mut var_token_idxs := [p.cur_tok_index()] - mut var_mut := [is_mut] // add first var mut - mut var_names := [p.check_name()] // add first variable - mut new_vars := 0 - if var_names[0] != '_' && !p.known_var(var_names[0]) { - new_vars++ - } - // more than 1 vars (multiple returns) - for p.tok == .comma { - p.check(.comma) - if p.tok == .key_mut { - p.check(.key_mut) - p.fspace() - var_mut << true - } - else { - var_mut << false - } - var_token_idxs << p.cur_tok_index() - var_name := p.check_name() - if var_name != '_' && !p.known_var(var_name) { - new_vars++ - } - var_names << var_name - } - is_assign := p.tok == .assign - is_decl_assign := p.tok == .decl_assign - if is_assign { - p.check_space(.assign) // = - } - else if is_decl_assign { - p.check_space(.decl_assign) // := - } - else { - p.error('expected `=` or `:=`') - } - expr_tok := p.cur_tok_index() - // all vars on left of `:=` already defined (or `_`) - if is_decl_assign && var_names.len == 1 && var_names[0] == '_' { - p.error_with_token_index('use `=` instead of `:=`', var_token_idxs.last()) - } - p.var_decl_name = if var_names.len > 1 { '_V_mret_${p.token_idx}_' + var_names.join('_') } else { var_names[0] } - t := p.gen_var_decl(p.var_decl_name, is_static) - if t == 'void' { - _,fn_name := p.is_expr_fn_call(expr_tok + 1) - p.error_with_token_index('${fn_name}() $err_used_as_value', expr_tok) - } - mut var_types := [t] - // multiple returns types - if var_names.len > 1 { - var_types = t.replace('_V_MulRet_', '').replace('_PTR_', '*').split('_V_') - } - // mismatched number of return & assignment vars - if var_names.len != var_types.len { - mr_fn := p.cgen.cur_line.find_between('=', '(').trim_space() - p.error_with_token_index('assignment mismatch: ${var_names.len} variables but `$mr_fn` returns $var_types.len values', var_token_idxs.last()) - } - for i, var_name in var_names { - var_token_idx := var_token_idxs[i] - var_is_mut := var_mut[i] - var_type := var_types[i] - known_var := p.known_var(var_name) - if var_name == '_' { - if var_is_mut { - p.error_with_token_index('`mut` has no effect here', var_token_idx - 1) - } - continue - } - // println('var decl tok=${p.strtok()} name=type=$var_name type=$var_type ismut=$var_is_mut') - // var decl, already exists (shadowing is not allowed) - // Don't allow declaring a variable with the same name. Even in a child scope - // if var_names.len == 1 && !p.builtin_mod && known_var { - if is_decl_assign && known_var { - p.error_with_token_index('redefinition of `$var_name`', var_token_idx) - } - // mut specified with assignment - // if /*is_assign && implicit*/ known_var && var_is_mut { - if known_var && var_is_mut { - p.error_with_token_index('cannot specify mutability for existing var `$var_name`, only for new vars', var_token_idx) - } - // assignment, but var does not exist - if is_assign && !known_var { - suggested := p.find_misspelled_local_var(var_name, 50) - if suggested != '' { - p.error_with_token_index('undefined: `$var_name`. did you mean:$suggested', var_token_idx) - } - p.error_with_token_index('undefined: `$var_name`.', var_token_idx) - } - if var_name.len > 1 && contains_capital(var_name) { - p.error_with_token_index('variable names cannot contain uppercase letters, use snake_case instead', var_token_idx) - } - // multiple return - if var_names.len > 1 { - p.gen(';\n') - // assigment - // if !p.builtin_mod && known_var { - if known_var { - v := p.find_var(var_name) or { - p.error_with_token_index('cannot find `$var_name`', var_token_idx) - break - } - p.check_types_with_token_index(var_type, v.typ, var_token_idx) - if !v.is_mut { - p.error_with_token_index('`$v.name` is immutable', var_token_idx) - } - p.mark_var_used(v) - p.mark_var_changed(v) - p.gen('$var_name = ${p.var_decl_name}.var_$i') - continue - } - // declaration - p.gen('$var_type $var_name = ${p.var_decl_name}.var_$i') - } - // Function bodies are always parsed once in the second pass, but - // script programs are parsed in the first pass, need to handle that. - if p.pass == .main { - p.register_var(Var{ - name: var_name - typ: var_type - is_mut: var_is_mut - is_alloc: p.is_alloc || var_type.starts_with('array_') - line_nr: p.tokens[var_token_idx].line_nr - token_idx: var_token_idx - }) - } - // if p.fileis('str.v') { - // if p.is_alloc { println('REG VAR IS ALLOC $name') } - // } - } - p.var_decl_name = '' - p.is_empty_c_struct_init = false -} - -fn (p mut Parser) get_struct_type(name_ string, is_c bool, is_ptr bool) string { - mut name := name_ - if is_ptr { - name += '*' // `&User{}` => type `User*` - } - if name in reserved_type_param_names { - p.warn('name `$name` is reserved for type parameters') - } - p.is_c_struct_init = is_c - return p.struct_init(name) -} - -fn (p mut Parser) get_var_type(name string, is_ptr bool, deref_nr int) string { - v := p.find_var_check_new_var(name) or { - return '' - } - if is_ptr { - p.gen('&') - } - else if deref_nr > 0 { - for _ in 0 .. deref_nr { - p.gen('*') - } - } - /* - if p.pref.autofree && v.typ == 'string' && v.is_arg && - p.assigned_type == 'string' { - p.warn('setting moved ' + v.typ) - p.mark_arg_moved(v) - } - */ - - mut typ := p.var_expr(v) - // *var - if deref_nr > 0 { - /* - if !p.inside_unsafe && !p.pref.building_v && p.mod != 'os' { - p.error('dereferencing can only be done inside an `unsafe` block') - } - */ - if !typ.contains('*') && !typ.ends_with('ptr') { - println('name="$name", t=$v.typ') - p.error('dereferencing requires a pointer, but got `$typ`') - } - for _ in 0 .. deref_nr { - typ = typ.replace_once('ptr', '') // TODO - typ = typ.replace_once('*', '') // TODO - } - } - // &var - else if is_ptr { - typ += '*' - } - if p.inside_return_expr { - // println('marking $v.name returned') - p.mark_var_returned(v) - // v.is_returned = true // TODO modifying a local variable - // that's not used afterwards, this should be a compilation - // error - } - return typ -} - -fn (p mut Parser) get_const_type(name string, is_ptr bool) string { - c := p.table.find_const(name) or { - return '' - } - if is_ptr && !c.is_global { - p.error('cannot take the address of constant `$c.name`') - } - else if is_ptr && c.is_global { - // c.ptr = true - p.gen('& /*const*/ ') - } - if !c.is_public && c.mod != p.mod { - p.warn('constant `$c.name` is private') - } - mut typ := p.var_expr(c) - if is_ptr { - typ += '*' - } - return typ -} - -fn (p mut Parser) get_c_func_type(name string) string { - f := Fn{ - name: name - is_c: true - } - p.is_c_fn_call = true - p.fn_call(mut f, 0, '', '') - p.is_c_fn_call = false - // C functions must be defined with `C.fn_name() fn_type` - cfn := p.table.find_fn(name) or { - // Is the user trying to do `var := C.foo()` or `bar(C.foo())` - // without declaring `foo`? - // Do not allow it. - if !name.starts_with('gl') && !name.starts_with('glad') { - p.error('undefined C function `$f.name`\n' + 'define it with `fn C.${name}([args]) [return_type]`') - } - return 'void*' - } - // println("C fn $name has type $cfn.typ") - return cfn.typ -} - -fn (p mut Parser) undefined_error(name string, orig_name string) { - name_dotted := mod_gen_name_rev(name.replace('__', '.')) - // check for misspelled function / variable / module / type - suggested := p.identify_typo(name) - if suggested.len != 0 { - p.error('undefined: `$name_dotted`. did you mean:\n$suggested\n') - } - // If orig_name is a mod, then printing undefined: `mod` tells us nothing - if p.table.known_mod(orig_name) || p.import_table.known_alias(orig_name) { - p.error('undefined: `$name_dotted` (in module `$orig_name`)') - } - else if orig_name in reserved_type_param_names { - p.error('the letter `$orig_name` is reserved for type parameters') - } - p.error('undefined: `$orig_name`') -} - -fn (p mut Parser) var_expr(v Var) string { - // p.log('\nvar_expr() v.name="$v.name" v.typ="$v.typ"') - // println('var expr is_tmp=$p.cgen.is_tmp\n') - if !v.is_const { - p.mark_var_used(v) - // `C.foo(&var)` means that `var` is changed. Mark it as changed - // to avoid `var was declared as mutable but was never changed` errors. - if p.calling_c && !v.is_changed { - // println('marking C var changed: $v.name') - p.mark_var_changed(v) - } - } - fn_ph := p.cgen.add_placeholder() - p.expr_var = v - p.gen(p.table.var_cgen_name(v.name)) - p.next() - mut typ := v.typ - // Function pointer? - if p.base_type(typ).starts_with('fn ') && p.tok == .lpar { - tt := p.table.find_type(p.base_type(typ)) - p.gen('(') - p.fn_call_args(mut tt.func, []) - p.gen(')') - typ = tt.func.typ - } - // users[0].name - if p.tok == .lsbr { - typ = p.index_expr(typ, fn_ph) - if p.base_type(typ).starts_with('fn ') && p.tok == .lpar { - tt := p.table.find_type(p.base_type(typ)) - p.gen('(') - p.fn_call_args(mut tt.func, []) - p.gen(')') - typ = tt.func.typ - } - } - // a.b.c().d chain - // mut dc := 0 - for p.tok == .dot { - if p.peek() == .key_select { - p.next() - return p.select_query(fn_ph) - } - if typ == 'pg__DB' && !p.fileis('pg.v') && p.peek() == .name { - name := p.tokens[p.token_idx].lit - if !name.contains('exec') && !name.starts_with('q_') { - p.next() - if name == 'insert' { - p.insert_query(fn_ph) - } - else if name == 'update' { - p.update_query(fn_ph) - } - return 'void' - } - } - // println('dot #$dc') - typ = p.dot(typ, fn_ph) - // p.log('typ after dot=$typ') - // print('tok after dot()') - // p.print_tok() - // dc++ - if p.tok == .lsbr { - typ = p.index_expr(typ, fn_ph) - } - } - // `a++` and `a--` - if p.tok == .inc || p.tok == .dec { - if !v.is_mut && !p.pref.translated { - p.error('`$v.name` is immutable') - } - if !v.is_changed { - p.mark_var_changed(v) - } - if typ != 'int' && !typ.contains('*') { - if !p.pref.translated && !is_number_type(typ) { - p.error('cannot ++/-- value of type `$typ`') - } - } - p.gen(p.tok.str()) - p.next() // ++/-- - // allow `a := c++` in translated code TODO remove once c2v handles this - if p.pref.translated {} - // return p.index_expr(typ, fn_ph) - else { - return typ - } - } - typ = p.index_expr(typ, fn_ph) - // TODO hack to allow `foo.bar[0] = 2` - if p.tok == .dot { - for p.tok == .dot { - typ = p.dot(typ, fn_ph) - } - typ = p.index_expr(typ, fn_ph) - } - return typ -} - -// user.name => `str_typ` is `User` -// user.company.name => `str_typ` is `Company` -fn (p mut Parser) dot(str_typ_ string, method_ph int) string { - // if p.fileis('orm_test') { - // println('ORM dot $str_typ') - // } - str_typ := str_typ_ - p.check(.dot) - is_variadic_arg := str_typ.starts_with('varg_') - typ := p.find_type(str_typ) - if typ.name.len == 0 { - p.error('dot(): cannot find type `$str_typ`') - } - // foo.$action() - if p.tok == .dollar { - p.comptime_method_call(typ) - return 'void' - } - field_name := p.lit - if field_name == 'filter' && str_typ.starts_with('array_') { - p.gen_array_filter(str_typ, method_ph) - return str_typ - } - else if field_name == 'map' && str_typ.starts_with('array_') { - return p.gen_array_map(str_typ, method_ph) - } - fname_tidx := p.cur_tok_index() - // 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()}') - // } - has_field := p.table.type_has_field(typ, p.table.var_cgen_name(field_name)) - mut has_method := p.table.type_has_method(typ, field_name) - if is_variadic_arg && field_name == 'len' { - p.gen('->$field_name') - p.next() - return 'int' - } - // generate `.str()` - if !has_method && field_name == 'str' && typ.name.starts_with('array_') { - p.gen_array_str(typ) - has_method = true - } - if !typ.is_c && !p.is_c_fn_call && !has_field && !has_method && !p.first_pass() { - if typ.name.starts_with('Option_') { - opt_type := typ.name[7..].replace('ptr_', '&') - p.error('unhandled option type: `?$opt_type`') - } - // println('error in dot():') - // println('fields:') - // for field in typ.fields { - // println(field.name) - // } - // println('methods:') - // for field in typ.methods { - // println(field.name) - // } - // println('str_typ=="$str_typ"') - p.error_with_token_index('type `$typ.name` has no field or method `$field_name`', fname_tidx) - } - mut dot := '.' - if str_typ.ends_with('*') || str_typ == 'FT_Face' { - // TODO fix C ptr typedefs - dot = dot_ptr - } - // 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 { - p.error_with_token_index('missing field: $struct_field in type $typ.name', fname_tidx) - exit(1) - } - if !field.is_mut && !p.has_immutable_field { - p.has_immutable_field = true - p.first_immutable_field = field - } - // Is the next token `=`, `+=` etc? (Are we modifying the field?) - next := p.peek() - modifying := next.is_assign() || next == .inc || next == .dec || (field.typ.starts_with('array_') && next == .left_shift) - if modifying { - p.expected_type = field.typ - } - if !p.builtin_mod && !p.pref.translated && modifying && p.has_immutable_field { - f := p.first_immutable_field - p.error_with_token_index('cannot modify immutable field `$f.name` (type `$f.parent_fn`)\n' + 'declare the field with `mut:` -struct $f.parent_fn { -mut: - $f.name $f.typ -} -', fname_tidx) - } - // Don't allow `arr.data` - if field.access_mod == .private && !p.builtin_mod && !p.pref.translated && p.mod != typ.mod && !p.is_vgen { - // println('$typ.name :: $field.name ') - // println(field.access_mod) - p.error_with_token_index('cannot refer to unexported field `$struct_field` (type `$typ.name`)\n' + 'declare the field with `pub:` -struct $typ.name { -pub: - $struct_field $field.typ -} -', fname_tidx) - } - base := p.base_type(field.typ) - if base.starts_with('fn ') && p.peek() == .lpar { - tmp_typ := p.table.find_type(base) - mut f := tmp_typ.func - p.gen('$dot$field.name') - p.gen('(') - p.check(.name) - p.fn_call_args(mut f, []) - p.gen(')') - return f.typ - } - p.gen(dot + struct_field) - p.next() - return field.typ - } - // method - mut method := p.table.find_method(typ, field_name) or { - p.error_with_token_index('could not find method `$field_name`', fname_tidx) // should never happen - exit(1) - } - p.fn_call(mut method, method_ph, '', str_typ) - // optional method call `a.method() or {}`, no return assignment - is_or_else := p.tok == .key_orelse - if is_or_else { - p.fspace() - } - if p.tok == .question { - // `files := os.ls('.')?` - return p.gen_handle_question_suffix(method, method_ph) - } - else if !p.is_var_decl && is_or_else { - method.typ = p.gen_handle_option_or_else(method.typ, '', method_ph) - } - else if !p.is_var_decl && !is_or_else && !p.inside_return_expr && method.typ.starts_with('Option_') { - opt_type := method.typ[7..].replace('ptr_', '&') - p.error('unhandled option type: `?$opt_type`') - } - // Methods returning `array` should return `array_string` etc - if method.typ == 'array' && typ.name.starts_with('array_') { - return typ.name - } - // Array methods returning `voidptr` (like `last()`) should return element type - if method.typ == 'void*' && typ.name.starts_with('array_') { - return parse_pointer(typ.name[6..]) - } - // if false && p.tok == .lsbr { - // if is_indexer { - // return p.index_expr(method.typ, method_ph) - // } - if method.typ.ends_with('*') { - p.is_alloc = true - } - return method.typ -} - -enum IndexType { - noindex - str - map - array - array0 - fixed_array - ptr -} - -fn get_index_type(typ string) IndexType { - if typ.starts_with('map_') { - return .map - } - if typ == 'string' { - return .str - } - if typ.starts_with('array_') || typ == 'array' { - return .array - } - if typ == 'byte*' || typ == 'byteptr' || typ.contains('*') { - return .ptr - } - if typ[0] == `[` { - return .fixed_array - } - return .noindex -} - -fn (p mut Parser) index_expr(typ_ string, fn_ph int) string { - mut typ := typ_ - // a[0] - v := p.expr_var - // if p.fileis('fn_test.v') { - // println('index expr typ=$typ') - // println(v.name) - // } - is_variadic_arg := typ.starts_with('varg_') - is_map := typ.starts_with('map_') - is_str := typ == 'string' - is_arr0 := typ.starts_with('array_') - is_arr := is_arr0 || typ == 'array' - is_ptr := typ == 'byte*' || typ == 'byteptr' || typ.contains('*') - mut is_slice := false - is_indexer := p.tok == .lsbr - mut close_bracket := false - index_error_tok_pos := p.token_idx - if is_indexer { - is_fixed_arr := typ[0] == `[` - if !is_str && !is_arr && !is_map && !is_ptr && !is_fixed_arr && !is_variadic_arg { - p.error('invalid operation: type `$typ` does not support indexing') - } - p.check(.lsbr) - // Get element type (set `typ` to it) - if is_str { - typ = 'byte' - // Direct faster access to .str[i] in builtin modules - if p.builtin_mod || p.pref.is_bare { - p.gen('.str[') - close_bracket = true - } - else { - // Bounds check everywhere else - p.gen(', ') - } - } - if is_variadic_arg { - typ = typ[5..] - } - if is_fixed_arr { - // `[10]int` => `int`, `[10][3]int` => `[3]int` - if typ.contains('][') { - pos := typ.index_after('[', 1) - typ = typ[pos..] - } - else { - typ = typ.all_after(']') - } - p.gen('[') - close_bracket = true - } - else if is_ptr && !is_variadic_arg { - // typ = 'byte' - typ = typ.replace('*', '') - // modify(mut []string) fix - if !is_arr && !is_map { - p.gen('[/*ptr!*/') - close_bracket = true - } - } - if is_arr { - if is_arr0 { - typ = parse_pointer(typ[6..]) - } - p.gen_array_at(typ, is_arr0, fn_ph) - } - // map is tricky - // need to replace "m[key] = val" with "tmp = val; map_set(&m, key, &tmp)" - // need to replace "m[key]" with "tmp = val; map_get(&m, key, &tmp)" - // can only do that later once we know whether there's an "=" or not - if is_map { - typ = typ.replace('map_', '') - typ = parse_pointer(typ) - if typ == 'map' { - typ = 'void*' - } - p.gen(',') - } - // expression inside [ ] - if is_arr || is_str { - // [2.. - if p.tok != .dotdot { - index_pos := p.cgen.cur_line.len - tt := p.table.find_type(p.expression()) - // Allows only i8-64 and byte-64 to be used when accessing an array - if tt.parent != 'int' && tt.parent != 'u32' { - p.check_types(tt.name, 'int') - } - if p.cgen.cur_line[index_pos..].replace(' ', '').int() < 0 { - p.error('cannot access negative array index') - } - } - // [.. - else { - p.gen('0') - } - if p.tok == .dotdot { - if is_arr { - typ = 'array_' + stringify_pointer(typ) - } - else if is_str { - typ = 'string' - } - else { - p.error('slicing is supported by arrays and strings only') - } - is_slice = true - p.next() - p.gen(',') - // ..4] - if p.tok != .rsbr { - p.check_types(p.expression(), 'int') - p.gen(', false') - } - // ..] - else { - p.gen('-1, true') - } - } - } - else { - tt := p.table.find_type(p.expression()) - // TODO: Get the key type of the map instead of only string. - if is_map && tt.parent != 'string' { - p.check_types(tt.name, 'string') - } - } - p.check(.rsbr) - // if (is_str && p.builtin_mod) || is_ptr || is_fixed_arr && ! (is_ptr && is_arr) { - if close_bracket { - p.gen(']/*r$typ $v.is_mut*/') - } - p.expr_var = v - } - // accessing variadiac args - if is_variadic_arg { - // TODO: why was this here? - // if p.calling_c { - // p.error('you cannot currently pass varg to a C function.') - // } - if is_indexer { - l := p.cgen.cur_line.trim_space() - idx := l.last_index(' ') or { - panic('idx') - } - index_val := l[idx..].trim_space() - p.cgen.resetln(l[..fn_ph]) - p.table.varg_access << VargAccess{ - fn_name: p.cur_fn.name - tok_idx: index_error_tok_pos - index: index_val.int() - } - p.cgen.set_placeholder(fn_ph, '${v.name}->args[$index_val]') - return typ - } - } - // `m[key] = val` - // TODO move this from index_expr() - if (p.tok == .assign && !p.is_sql) || p.tok.is_assign() { - if is_indexer && is_str && !p.builtin_mod { - p.error('strings are immutable') - } - p.assigned_type = typ - p.expected_type = typ - assign_pos := p.cgen.cur_line.len - is_cao := p.tok != .assign - p.assign_statement(v, fn_ph, is_indexer && (is_map || is_arr)) - // `m[key] = val` - if is_indexer && (is_map || is_arr) { - p.gen_array_set(typ, is_ptr, is_map, fn_ph, assign_pos, is_cao) - } - return typ - } - // else if p.pref.is_verbose && p.assigned_var != '' { - // p.error('didnt assign') - // } - // `m[key]`. no =, just a getter - else if (is_map || is_arr || (is_str && !p.builtin_mod)) && is_indexer { - typ = parse_pointer(typ) - p.index_get(typ, fn_ph, IndexConfig{ - is_arr: is_arr - is_map: is_map - is_ptr: is_ptr - is_str: is_str - is_slice: is_slice - }) - } - // else if is_arr && is_indexer{} - return typ -} - -struct IndexConfig { - is_map bool - is_str bool - is_ptr bool - is_arr bool - is_arr0 bool - is_slice bool -} - -// for debugging only -fn (p &Parser) fileis(s string) bool { - return os.file_name(p.scanner.file_path).contains(s) -} - -// in and dot have higher priority than `!` -fn (p mut Parser) indot_expr() string { - ph := p.cgen.add_placeholder() - mut typ := p.term() - if p.tok == .dot { - for p.tok == .dot { - typ = p.dot(typ, ph) - } - } - // `a in [1, 2, 3]` - // `key in map` - if p.tok == .key_in { - p.fspace() - p.check(.key_in) - p.expected_type = typ // this allows short enum syntax `foo in [.val1, .val2, .val3]` - if p.tok == .lsbr { - // a in [1,2,3] optimization => `a == 1 || a == 2 || a == 3` - // avoids an allocation - p.fspace() - p.in_optimization(typ, ph) - return 'bool' - } - p.fspace() - p.gen('), ') - arr_typ := p.expression() - is_map := arr_typ.starts_with('map_') - is_arr := arr_typ.starts_with('array_') - if !is_arr && !is_map { - p.error('`in` requires an array/map') - } - if is_arr && parse_pointer(arr_typ[6..]) != typ { - p.error('bad element type: `$typ` in `$arr_typ`') - } - if is_map && typ != 'string' { - p.error('bad element type: expecting `string`') - } - arr_typ2 := p.table.find_type(arr_typ) - if !is_map && !arr_typ2.has_method('contains') { - p.error('$arr_typ has no method `contains`') - } - // `typ` is element's type - if is_map { - p.cgen.set_placeholder(ph, '(_IN_MAP( (') - } - else { - p.cgen.set_placeholder(ph, '(_IN($typ, (') - } - p.gen('))') - return 'bool' - } - return typ -} - -// { user | name: 'new name' } -fn (p mut Parser) assoc() string { - // println('assoc()') - p.next() - name := p.check_name() - p.fspace() - var := p.find_var_or_const(name) or { - p.error('unknown variable `$name`') - exit(1) - } - if !var.is_const { - p.mark_var_used(var) - } - p.check(.pipe) - p.fgen_nl() - p.gen('($var.typ){') - typ := p.table.find_type(var.typ) - mut fields := []string // track the fields user is setting, the rest will be copied from the old object - for p.tok != .rcbr { - field := p.check_name() - // if !typ.has_field(field) { - f := typ.find_field(field) or { - p.error('`$typ.name` has no field `$field`') - exit(1) - } - fields << field - p.gen('.$field = ') - p.check(.colon) - p.check_types(p.bool_expression(), f.typ) - p.gen(',') - if p.tok != .rcbr { - if p.tok == .comma { - p.check(.comma) - } - } - p.fgen_nl() - } - // Copy the rest of the fields - for ffield in typ.fields { - f := ffield.name - if f in fields { - continue - } - p.gen('.$f = ${var.name}.$f,') - } - p.check(.rcbr) - p.gen('}') - return var.typ -} - -fn (p mut Parser) char_expr() { - p.gen("\'$p.lit\'") - p.next() -} - -fn format_str(_str string) string { - // TODO don't call replace 3 times for every string, do this in scanner.v - mut str := _str.replace('"', '\\"') - str = str.replace('\r\n', '\\n') - str = str.replace('\n', '\\n') - return str -} - -// m := map[string]int{} -// m := { 'one': 1 } -fn (p mut Parser) map_init() string { - // m := { 'one': 1, 'two': 2 } - mut keys_gen := '' // (string[]){tos2("one"), tos2("two")} - mut vals_gen := '' // (int[]){1, 2} - mut val_type := '' // 'int' - if p.tok == .lcbr { - p.check(.lcbr) - mut i := 0 - for { - key := p.lit - keys_gen += 'tos3("$key"), ' - p.check(.string) - p.check(.colon) - p.fspace() - t,val_expr := p.tmp_expr() - if i == 0 { - val_type = t - } - i++ - if val_type != t { - if !p.check_types_no_throw(val_type, t) { - p.error('bad map element type `$val_type` instead of `$t`') - } - } - vals_gen += '$val_expr, ' - if p.tok == .rcbr { - p.fgen_nl() - p.check(.rcbr) - break - } - if p.tok == .comma { - p.check(.comma) - } - p.fgen_nl() - } - p.gen('new_map_init($i, sizeof($val_type), ' + '(string[$i]){ $keys_gen }, ($val_type [$i]){ $vals_gen } )') - typ := 'map_${stringify_pointer(val_type)}' - p.register_map(typ) - return typ - } - p.next() - p.check(.lsbr) - key_type := p.check_name() - if key_type != 'string' { - p.error('only string key maps allowed for now') - } - p.check(.rsbr) - val_type = p.get_type() // / p.check_name() - // if !p.table.known_type(val_type) { - // p.error('map init unknown type "$val_type"') - // } - typ := 'map_${stringify_pointer(val_type)}' - p.register_map(typ) - p.gen('new_map(1, sizeof($val_type))') - if p.tok == .lcbr { - p.check(.lcbr) - p.check(.rcbr) - println('warning: $p.file_name:$p.scanner.line_nr ' + 'initializaing maps no longer requires `{}`') - } - return typ -} - -// `nums := [1, 2, 3]` -fn (p mut Parser) array_init() string { - expected_array_type := p.expected_type - // if p.fileis('interface_') { - // println('a exp='+p.expected_type) - // } - p.is_alloc = true - p.check(.lsbr) - mut is_integer := p.tok == .number // for `[10]int` - // fixed length arrays with a const len: `nums := [N]int`, same as `[10]int` basically - mut is_const_len := false - if p.tok == .name && !p.inside_const { - const_name := p.prepend_mod(p.lit) - if p.table.known_const(const_name) { - c := p.table.find_const(const_name) or { - p.error('unknown const `$const_name`') - exit(1) - } - if c.typ == 'int' && p.peek() == .rsbr { - // && !p.inside_const { - is_integer = true - is_const_len = true - } - else { - p.error('bad fixed size array const `$p.lit`') - } - } - } - lit := p.lit - mut typ := '' - new_arr_ph := p.cgen.add_placeholder() - mut i := 0 - for p.tok != .rsbr { - if expected_array_type.starts_with('array_') { - p.expected_type = expected_array_type[6..] - } - val_typ := p.bool_expression() - // Get the type of the first expression - if i == 0 { - typ = val_typ - // fixed width array initialization? (`arr := [20]byte`) - if is_integer && p.tok == .rsbr && p.peek() == .name && p.cur_tok().line_nr == p.peek_token().line_nr { - // there is no space between `[10]` and `byte` - // if p.cur_tok().col + p.peek_token().lit.len == p.peek_token().col { - if p.cur_tok().pos + p.peek_token().lit.len == p.peek_token().pos { - p.check(.rsbr) - // `[10]C.kevent` needs `struct ` - is_c := p.tok == .name && p.lit == 'C' - if is_c { - p.cgen.insert_before('struct ') - } - array_elem_typ := p.get_type() - if !p.table.known_type(array_elem_typ) { - p.error('bad type `$array_elem_typ`') - } - p.cgen.resetln('') - // p.gen('{0}') - p.is_alloc = false - if is_const_len { - return '[${mod_gen_name(p.mod)}__$lit]$array_elem_typ' - } - return '[$lit]$array_elem_typ' - } - else { - p.check(.rsbr) - typ = p.get_type() - p.error('no space allowed between [$lit] and $typ') - } - } - } - if val_typ != typ { - if !p.check_types_no_throw(val_typ, typ) { - mut ok := false - // `foo([cat, dog])` where foo is `fn foo([]Animal) {` - // `expected_type` is `[]Animaler` - if expected_array_type.ends_with('er') { - if p.satisfies_interface(expected_array_type, typ, false) { - ok = true - } - } - if !ok { - p.error('bad array element type `$val_typ` instead of `$typ`') - } - } - } - if p.tok != .rsbr && p.tok != .semicolon { - p.gen(', ') - line_nr := p.tok - p.check(.comma) - p.fspace_or_newline() - } - i++ - // Repeat (a = [0;5] ) - if i == 1 && p.tok == .semicolon { - p.error('`[0 ; len]` syntax was removed. Use `[0].repeat(len)` instead') - } - } - p.check(.rsbr) - // type after `]`? (e.g. "[]string") - exp_array := p.expected_type.starts_with('array_') - if p.tok != .name && p.tok != .mul && p.tok != .lsbr && p.tok != .amp && i == 0 && !exp_array { - p.error('specify array type: `[]typ` instead of `[]`') - } - if i == 0 && (p.tok == .name || p.tok == .mul || p.tok == .amp) && p.tokens[p.token_idx - 2].line_nr == p.tokens[p.token_idx - 1].line_nr { - // TODO - // vals.len == 0 { - if exp_array { - type_expected := p.expected_type[6..].replace('ptr_', '&') - p.warn('no need to specify the full array type here, use `[]` instead of `[]$type_expected`') - } - typ = p.get_type() - } - else if exp_array && i == 0 { - // allow `known_array = []` - typ = p.expected_type[6..] - } - // ! after array => no malloc and no copy - no_alloc := p.tok == .not - if no_alloc { - p.next() - } - // [1,2,3]!! => [3]int{1,2,3} - is_fixed_size := p.tok == .not - if is_fixed_size { - p.next() - p.gen(' }') - 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 { - p.cgen.set_placeholder(new_arr_ph, '{') - } - else { - p.cgen.set_placeholder(new_arr_ph, '($typ[]) {') - } - } - return '[$i]$typ' - } - // if ptr { - // typ += '_ptr" - // } - real := parse_pointer(typ) - p.gen_array_init(real, no_alloc, new_arr_ph, i) - typ = 'array_${stringify_pointer(typ)}' - p.register_array(typ) - if p.tok == .lcbr && i == 0 && p.peek() == .name { - // []string{len:10} (V2) - for p.tok != .rcbr { - p.next() - } - p.check(.rcbr) - } - return typ -} - -// `f32(3)` -// tok is `f32` or `)` if `(*int)(ptr)` -fn (p mut Parser) get_tmp() string { - p.tmp_cnt++ - return 'tmp$p.tmp_cnt' -} - -fn (p mut Parser) get_tmp_counter() int { - p.tmp_cnt++ - return p.tmp_cnt -} - -fn (p mut Parser) assert_statement() { - if p.first_pass() { - return - } - p.check(.key_assert) - p.fspace() - tmp := p.get_tmp() - p.gen('bool $tmp = ') - p.check_types(p.bool_expression(), 'bool') - nline := p.scanner.line_nr - // TODO print "expected: got" for failed tests - filename := cescaped_path(p.file_path) - cfname := p.cur_fn.name.replace('main__', '') - sourceline := p.scanner.line(nline - 1).replace('"', "\'") - if !p.pref.is_test { - // an assert used in a normal v program. no fancy formatting - p.genln(';\n -/// sline: "$sourceline" -if (!$tmp) { - g_test_fails++; - eprintln(tos3("${filename}:${p.scanner.line_nr}: FAILED: ${cfname}()")); - eprintln(tos3("Source: $sourceline")); - v_panic(tos3("An assertion failed.")); - exit(1); -} else { - g_test_oks++; -} -') - return - } - p.genln(';\n -if (!$tmp) { - g_test_fails++; - main__cb_assertion_failed( - tos3("$filename"), - $p.scanner.line_nr, - tos3("$sourceline"), - tos3("${p.cur_fn.name}()") - ); - exit(1); - // TODO - // Maybe print all vars in a test function if it fails? -} else { - g_test_oks++; - main__cb_assertion_ok( - tos3("$filename"), - $p.scanner.line_nr, - tos3("$sourceline"), - tos3("${p.cur_fn.name}()") - ); -} - -') -} - -fn (p mut Parser) return_st() { - p.check(.key_return) - p.fspace() - deferred_text := p.get_deferred_text() - fn_returns := p.cur_fn.typ != 'void' - if fn_returns { - if p.tok == .rcbr { - p.error('`$p.cur_fn.name` needs to return `$p.cur_fn.typ`') - } - ph := p.cgen.add_placeholder() - p.inside_return_expr = true - is_none := p.tok == .key_none - p.expected_type = p.cur_fn.typ - mut expr_type := p.bool_expression() - mut expr_type_chk := expr_type - // println('$p.cur_fn.name returns type $expr_type, should be $p.cur_fn.typ') - mut types := []string - mut mr_values := [p.cgen.cur_line[ph..].trim_space()] - types << expr_type - for p.tok == .comma { - p.check(.comma) - typ,expr := p.tmp_expr() - types << typ - mr_values << expr.trim_space() - } - mut cur_fn_typ_chk := p.cur_fn.typ - // multiple returns - if types.len > 1 { - mr_type := if p.cur_fn.typ.starts_with('Option_') { p.cur_fn.typ[7..] } else { p.cur_fn.typ } - expr_type = mr_type - expr_type_chk = types.join(',') - cur_fn_typ_chk = cur_fn_typ_chk.replace('_V_MulRet_', '').replace('_PTR_', '*').replace('_V_', ',') - mut ret_fields := '' - for ret_val_idx, ret_val in mr_values { - if ret_val_idx > 0 { - ret_fields += ',' - } - ret_fields += '.var_$ret_val_idx=${ret_val}' - } - p.cgen.resetln('($mr_type){$ret_fields}') - } - p.inside_return_expr = false - // Automatically wrap an object inside an option if the function - // returns an option: - // `return val` => `return opt_ok(val)` - if p.cur_fn.typ.ends_with(stringify_pointer(expr_type)) && !is_none && p.cur_fn.typ.starts_with('Option_') { - tmp := p.get_tmp() - ret := p.cgen.cur_line[ph..] - typ := parse_pointer(expr_type.replace('Option_', '')) - p.cgen.resetln('$expr_type $tmp = OPTION_CAST($expr_type)($ret);') - p.genln(deferred_text) - p.gen('return opt_ok(&$tmp, sizeof($typ))') - } - else { - ret := p.cgen.cur_line[ph..] - if deferred_text == '' || expr_type == 'void*' { - // no defer{} necessary? - if expr_type == '${p.cur_fn.typ}*' { - p.cgen.resetln('return *$ret') - } - else { - p.cgen.resetln('return $ret') - } - } - else { - tmp := p.get_tmp() - p.cgen.resetln('$expr_type $tmp = $ret;\n') - p.genln(deferred_text) - p.genln('return $tmp;') - } - } - p.check_types(expr_type_chk, cur_fn_typ_chk) - } - else { - // Don't allow `return val` in functions that don't return anything - if p.tok == .name || p.tok == .number || p.tok == .string{ - p.error_with_token_index('function `$p.cur_fn.name` should not return a value', p.cur_fn.fn_name_token_idx) - } - p.genln(deferred_text) - if p.cur_fn.name == 'main' { - p.gen('return 0') - } - else { - p.gen('return') - } - } - // p.fgenln('//ret') - p.returns = true -} - -fn (p &Parser) get_deferred_text() string { - // @emily33901: Scoped defer - // Check all of our defer texts to see if there is one at a higher scope level - // The one for our current scope would be the last so any before that need to be - // added. - mut deferred_text := '' - for text in p.cur_fn.defer_text { - if text != '' { - // In reverse order - deferred_text = text + deferred_text - } - } - return deferred_text -} - -fn prepend_mod(mod, name string) string { - return '${mod}__${name}' -} - -fn (p &Parser) prepend_mod(name string) string { - return prepend_mod(mod_gen_name(p.mod), name) -} - -fn (p mut Parser) go_statement() { - p.check(.key_go) - p.fspace() - mut gotoken_idx := p.cur_tok_index() - // TODO copypasta of name_expr() ? - if p.peek() == .dot { - // Method - var_name := p.lit - v := p.find_var(var_name) or { - return - } - p.mark_var_used(v) - gotoken_idx = p.cur_tok_index() - p.next() - p.check(.dot) - typ := p.table.find_type(v.typ) - method := p.table.find_method(typ, p.lit) or { - p.error_with_token_index('go method missing $var_name', gotoken_idx) - return - } - p.async_fn_call(method, 0, var_name, v.typ) - } - else { - 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_token_index('can not find function $f_name', gotoken_idx) - return - } - if f.name == 'println' || f.name == 'print' { - p.error_with_token_index('`go` cannot be used with `println`', gotoken_idx) - } - p.async_fn_call(f, 0, '', '') - } -} - -/* -fn (p mut Parser) register_var(v Var) { - if v.line_nr == 0 { - spos := p.scanner.get_scanner_pos() - p.register_var({ v | scanner_pos: spos, line_nr: spos.line_nr }) - } else { - p.register_var(v) - } -} -*/ - -// user:=jsdecode(User, user_json_string) -fn (p mut Parser) js_decode() string { - p.check(.name) // json - p.check(.dot) - op := p.check_name() - op_token_idx := p.cur_tok_index() - if op == 'decode' { - // User tmp2; tmp2.foo = 0; tmp2.bar = 0;// I forgot to zero vals before => huge bug - // Option_User tmp3 = jsdecode_User(json_parse( s), &tmp2); ; - // if (!tmp3 .ok) { - // return - // } - // User u = *(User*) tmp3 . data; // TODO remove this (generated in or {} block handler) - p.check(.lpar) - typ := p.get_type() - p.check(.comma) - styp,expr := p.tmp_expr() - p.check_types(styp, 'string') - p.check(.rpar) - tmp := p.get_tmp() - cjson_tmp := p.get_tmp() - mut decl := '$typ $tmp; ' - // Init the struct - tt := p.table.find_type(typ) - for field in tt.fields { - def_val := type_default(field.typ) - if def_val != '' { - decl += '${tmp}.$field.name = OPTION_CAST($field.typ) $def_val;\n' - } - } - p.gen_json_for_type(tt) - decl += 'cJSON* $cjson_tmp = json__json_parse($expr);' - p.cgen.insert_before(decl) - // p.gen('jsdecode_$typ(json_parse($expr), &$tmp);') - p.gen('json__jsdecode_${typ}($cjson_tmp, &$tmp); cJSON_Delete($cjson_tmp);') - opt_type := 'Option_$typ' - p.cgen.typedefs << 'typedef Option $opt_type;' - p.table.register_builtin(opt_type) - return opt_type - } - else if op == 'encode' { - p.check(.lpar) - typ,expr := p.tmp_expr() - tt := p.table.find_type(typ) - p.gen_json_for_type(tt) - p.check(.rpar) - p.gen('json__json_print(json__jsencode_${typ}($expr))') - return 'string' - } - else { - p.error_with_token_index('bad json op "$op"', op_token_idx) - } - return '' -} - -fn (p mut Parser) attribute() { - p.check(.lsbr) - if p.tok == .key_if { - // [if vfmt] - p.next() - p.fspace() - p.attr = 'if ' + p.check_name() - } - else { - p.attr = p.check_name() - } - attr_token_idx := p.cur_tok_index() - if p.tok == .colon { - p.check(.colon) - p.attr = p.attr + ':' + p.check_name() - } - p.check(.rsbr) - p.fgen_nl() - is_pub := p.tok == .key_pub - peek := p.peek() - if p.tok == .key_fn || (is_pub && peek == .key_fn) { - p.fn_decl() - p.attr = '' - return - } - else if p.tok == .key_struct || (is_pub && peek == .key_struct) { - p.struct_decl([]) - p.attr = '' - return - } - else if p.tok == .key_enum || (is_pub && peek == .key_enum) { - p.enum_decl(false) - p.attr = '' - return - } - p.error_with_token_index('bad attribute usage', attr_token_idx) -} - -fn (p mut Parser) defer_st() { - p.check(.key_defer) - p.fspace() - p.check(.lcbr) - pos := p.cgen.lines.len - // Save everything inside the defer block to `defer_text`. - // It will be inserted before every `return` - // Emily: TODO: all variables that are used in this defer statement need to be evaluated when the block - // is defined otherwise they could change over the course of the function - // (make temps out of them) - p.genln('{') - p.statements() - p.cur_fn.defer_text.last() = p.cgen.lines[pos..].join('\n') + p.cur_fn.defer_text.last() - // Rollback p.cgen.lines - p.cgen.lines = p.cgen.lines[..pos] - p.cgen.resetln('') -} - -fn (p mut Parser) check_and_register_used_imported_type(typ_name string) { - us_idx := typ_name.index('__') or { - return - } - mut arg_mod := typ_name[..us_idx] - if arg_mod.contains('_dot_') { - arg_mod = arg_mod.all_after('_dot_') - } - if p.import_table.known_alias(arg_mod) { - p.import_table.register_used_import(arg_mod) - } -} - -fn (p mut Parser) check_unused_imports() { - // Don't run in the generated V file with `.str()` - if p.is_vgen { - return - } - mut output := '' - for alias, mod in p.import_table.imports { - if !p.import_table.is_used_import(alias) { - mod_alias := if alias == mod { alias } else { '$alias ($mod)' } - output += '\n * $mod_alias' - } - } - if output == '' { - return - } - // the imports are usually at the start of the file - // p.production_error_with_token_index('the following imports were never used: $output', 0) - if p.pref.verbosity.is_higher_or_equal(.level_two) { - eprintln('Used imports table: ${p.import_table.used_imports.str()}') - } - p.warn('the following imports were never used: $output') -} - -fn (p &Parser) is_expr_fn_call(start_tok_idx int) (bool,string) { - mut expr := p.tokens[start_tok_idx - 1].str() - mut is_fn_call := p.tokens[start_tok_idx].tok == .lpar - if !is_fn_call { - mut i := start_tok_idx - for (p.tokens[i].tok == .dot || p.tokens[i].tok == .name) && p.tokens[i].lit != '_' && i < p.tokens.len { - expr += p.tokens[i].str() - i++ - } - is_fn_call = p.tokens[i].tok == .lpar - } - return is_fn_call,expr -} - -[inline] -// skip any block of code in curley braces `{}` -fn (p mut Parser) skip_block(inside_first_lcbr bool) { - mut cbr_depth := if inside_first_lcbr { 1 } else { 0 } - for { - if p.tok == .lcbr { - cbr_depth++ - } - if p.tok == .rcbr { - cbr_depth-- - if cbr_depth == 0 { - break - } - } - p.next() - } - p.check(.rcbr) -} - -fn todo_remove() {} - -// x64.new_gen('f') -// } -fn (p mut Parser) check_if_parser_is_stuck(parsing_cycle u64, parsing_start_ticks i64) { - // QTODO - p.warn('todo...') - /* - if p.prev_stuck_token_idx == p.token_idx { - // many many cycles have passed with no progress :-( ... - eprintln('Parsing is [probably] stuck. Cycle: ${parsing_cycle:12ld} .') - eprintln(' parsing file: ${p.file_path} | pass: ${p.pass} | mod: ${p.mod} | fn: ${p.cur_fn.name}') - p.print_current_tokens(' source') - if time.ticks() > parsing_start_ticks + 10*1000{ - p.warn('V compiling is too slow.') - } - if time.ticks() > parsing_start_ticks + 30*1000{ - p.error(' -V took more than 30 seconds to compile this file. -Please create a GitHub issue: https://github.com/vlang/v/issues/new/choose -') - } - } - p.prev_stuck_token_idx = p.token_idx - */ - -} diff --git a/vlib/compiler/asm.v b/vlib/compiler/asm.v deleted file mode 100644 index f4c283c91a..0000000000 --- a/vlib/compiler/asm.v +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -fn (p mut Parser) inline_asm() { - if !p.inside_unsafe { - p.error('asm() needs to be run inside `unsafe {}`') - } - p.next() - p.check(.lcbr) - s := p.check_string() - p.genln('asm("$s"') - for p.tok == .string{ - p.genln('"$p.lit"') - p.next() - } - for p.tok == .colon { - p.next() - arg := p.check_string() - p.gen(': "$arg"') - if p.tok == .lpar { - p.next() - var_name := p.check_name() - if !p.known_var(var_name) { - p.error('unknown variable `$var_name`') - } - p.check(.rpar) - p.genln('($var_name)') - } - } - p.genln(');') - p.check(.rcbr) -} - diff --git a/vlib/compiler/cgen.v b/vlib/compiler/cgen.v deleted file mode 100644 index 335e4b5b8b..0000000000 --- a/vlib/compiler/cgen.v +++ /dev/null @@ -1,534 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import ( - os - strings -) - -struct CGen { - out os.File - out_path string - // types []string - thread_fns []string - // buf strings.Builder - is_user bool -mut: - lines []string - lines_extra []string - typedefs []string - type_aliases []string - includes []string - thread_args []string - consts []string - const_defines []string - fns []string - so_fns []string - consts_init []string - pass Pass - nogen bool - prev_tmps []string - tmp_line string - cur_line string - prev_line string - is_tmp bool - fn_main string - stash string - file string - line int - line_directives bool - cut_pos int -} - -pub fn new_cgen(out_name_c string) &CGen { - path := out_name_c - out := os.create(path)or{ - println('failed to create $path') - return &CGen{ - } - } - return &CGen{ - out_path: path - out: out - // buf: strings.new_builder(10000) - - lines: make(0, 1000, sizeof(string)) - } - //return gen -} - -fn (g mut CGen) genln(s string) { - if g.nogen || g.pass != .main { - return - } - if g.is_tmp { - g.tmp_line = '$g.tmp_line $s\n' - return - } - g.cur_line = '$g.cur_line $s' - if g.cur_line != '' { - if g.line_directives && g.cur_line.trim_space() != '' { - if g.file.len > 0 && g.line > 0 { - g.lines << '\n#line $g.line "$g.file"' - } - } - g.lines << g.cur_line - g.prev_line = g.cur_line - g.cur_line = '' - } -} - -// same as `set_placeholder(0, s)`, but faster -fn (g mut CGen) prepend_to_statement(s string) { - if g.is_tmp { - g.tmp_line = s + g.tmp_line - return - } - g.lines << s - g.prev_line = g.cur_line -} - -fn (g mut CGen) gen(s string) { - if g.nogen || g.pass != .main { - return - } - if g.is_tmp { - g.tmp_line = '$g.tmp_line $s' - } - else { - g.cur_line = '$g.cur_line $s' - } -} - -fn (g mut CGen) resetln(s string) { - if g.nogen || g.pass != .main { - return - } - if g.is_tmp { - g.tmp_line = s - } - else { - g.cur_line = s - } -} - -fn (g mut CGen) save() { - s := g.lines.join('\n') - g.out.writeln(s) - g.out.writeln(g.lines_extra.join('\n')) - g.out.close() -} - -// returns expression's type, and entire expression's string representation) -fn (p mut Parser) tmp_expr() (string,string) { - // former start_tmp() - if p.cgen.is_tmp { - p.cgen.prev_tmps << p.cgen.tmp_line - } - // kg.tmp_lines_pos++ - p.cgen.tmp_line = '' - p.cgen.is_tmp = true - // - typ := p.bool_expression() - res := p.cgen.tmp_line - if p.cgen.prev_tmps.len > 0 { - p.cgen.tmp_line = p.cgen.prev_tmps.last() - p.cgen.prev_tmps = p.cgen.prev_tmps[0..p.cgen.prev_tmps.len - 1] - } - else { - p.cgen.tmp_line = '' - p.cgen.is_tmp = false - } - return typ,res -} - -fn (g &CGen) add_placeholder() int { - if g.is_tmp { - return g.tmp_line.len - } - return g.cur_line.len -} - -fn (g mut CGen) start_cut() { - g.cut_pos = g.add_placeholder() -} - -fn (g mut CGen) cut() string { - pos := g.cut_pos - g.cut_pos = 0 - if g.is_tmp { - res := g.tmp_line[pos..] - g.tmp_line = g.tmp_line[..pos] - return res - } - res := g.cur_line[pos..] - g.cur_line = g.cur_line[..pos] - return res -} - -fn (g mut CGen) set_placeholder(pos int, val string) { - if g.nogen || g.pass != .main { - return - } - // if pos == 0 { - // g.prepend_to_statement(val) - // return - // } - // g.lines.set(pos, val) - if g.is_tmp { - left := g.tmp_line[..pos] - right := g.tmp_line[pos..] - g.tmp_line = '${left}${val}${right}' - return - } - left := g.cur_line[..pos] - right := g.cur_line[pos..] - g.cur_line = '${left}${val}${right}' - // g.genln('') -} - -fn (g mut CGen) insert_before(val string) { - if g.nogen { - return - } - prev := g.lines[g.lines.len - 1] - g.lines[g.lines.len - 1] = '$prev \n $val \n' -} - -fn (g mut CGen) register_thread_fn(wrapper_name, wrapper_text, struct_text string) { - for arg in g.thread_args { - if arg.contains(wrapper_name) { - return - } - } - g.thread_args << struct_text - g.thread_args << wrapper_text -} - -fn (v &V) prof_counters() string { - res := []string - // Global fns - // for f in c.table.fns { - // res << 'double ${c.table.cgen_name(f)}_time;' - // } - // Methods - /* - for typ in c.table.types { - // println('') - for f in typ.methods { - // res << f.cgen_name() - res << 'double ${c.table.cgen_name(f)}_time;' - // println(f.cgen_name()) - } - } - */ - - return res.join(';\n') -} - -fn (p &Parser) print_prof_counters() string { - res := []string - // Global fns - // for f in p.table.fns { - // counter := '${p.table.cgen_name(f)}_time' - // res << 'if ($counter) printf("%%f : $f.name \\n", $counter);' - // } - // Methods - /* - for typ in p.table.types { - // println('') - for f in typ.methods { - counter := '${p.table.cgen_name(f)}_time' - res << 'if ($counter) printf("%%f : ${p.table.cgen_name(f)} \\n", $counter);' - // res << 'if ($counter) printf("$f.name : %%f\\n", $counter);' - // res << f.cgen_name() - // res << 'double ${f.cgen_name()}_time;' - // println(f.cgen_name()) - } - } - */ - - return res.join(';\n') -} - -fn (p mut Parser) gen_typedef(s string) { - if !p.first_pass() { - return - } - p.cgen.typedefs << s -} - -fn (p mut Parser) gen_type_alias(s string) { - if !p.first_pass() { - return - } - p.cgen.type_aliases << s -} - -fn (g mut CGen) add_to_main(s string) { - g.fn_main = g.fn_main + s -} - -fn (v &V) build_thirdparty_obj_file(path string, moduleflags []CFlag) { - obj_path := os.real_path(path) - if os.exists(obj_path) { - return - } - println('$obj_path not found, building it...') - parent := os.dir(obj_path) - files := os.ls(parent)or{ - panic(err) - } - mut cfiles := '' - for file in files { - if file.ends_with('.c') { - cfiles += '"' + os.real_path(parent + os.path_separator + file) + '" ' - } - } - btarget := moduleflags.c_options_before_target() - atarget := moduleflags.c_options_after_target() - cmd := '$v.pref.ccompiler $v.pref.third_party_option $btarget -c -o "$obj_path" $cfiles $atarget ' - res := os.exec(cmd)or{ - println('failed thirdparty object build cmd: $cmd') - verror(err) - return - } - if res.exit_code != 0 { - println('failed thirdparty object build cmd: $cmd') - verror(res.output) - return - } - println(res.output) -} - -fn os_name_to_ifdef(name string) string { - match name { - 'windows' { - return '_WIN32' - } - 'mac' { - return '__APPLE__' - } - 'macos' { - return '__APPLE__' - } - 'linux' { - return '__linux__' - } - 'freebsd' { - return '__FreeBSD__' - } - 'openbsd' { - return '__OpenBSD__' - } - 'netbsd' { - return '__NetBSD__' - } - 'dragonfly' { - return '__DragonFly__' - } - 'msvc' { - return '_MSC_VER' - } - 'android' { - return '__ANDROID__' - } - 'js' { - return '_VJS' - } - 'solaris' { - return '__sun' - } - 'haiku' { - return '__haiku__' - } - 'linux_or_macos' { - return '' - } - else { - verror('bad os ifdef name "$name"') - }} - // verror('bad os ifdef name "$name"') - return '' -} - -fn (v &V) platform_postfix_to_ifdefguard(name string) string { - if name.starts_with('custom '){ - cdefine := name.replace('custom ','') - return '#ifdef CUSTOM_DEFINE_${cdefine}' - } - s := match name { - '.v' { - '' - } // no guard needed - '_win.v', '_windows.v' { - '#ifdef _WIN32' - } - '_nix.v' { - '#ifndef _WIN32' - } - '_qnx.v' { - '#ifndef __QNX__' - } - '_lin.v', '_linux.v' { - '#ifdef __linux__' - } - '_mac.v', '_darwin.v' { - '#ifdef __APPLE__' - } - '_freebsd.v' { - '#ifdef __FreeBSD__' - } - '_openbsd.v' { - '#ifdef __OpenBSD__' - } - '_netbsd.v' { - '#ifdef __NetBSD__' - } - '_bsd.v' { - '#ifdef __FreeBSD__ || __NetBSD__ || __OpenBSD__' - } - '_solaris.v' { - '#ifdef __sun' - } - '_haiku.v' { - '#ifdef __haiku__' - } - else { - // verror('bad platform_postfix "$name"') - // TODO - ''}} - if s == '' { - verror('bad platform_postfix "$name"') - } - return s -} - -// C struct definitions, ordered -// Sort the types, make sure types that are referenced by other types -// are added before them. -fn (v &V) type_definitions() string { - mut types := []Type // structs that need to be sorted - mut builtin_types := []Type // builtin types - // builtin types need to be on top - builtins := ['string', 'array', 'KeyValue', 'DenseArray', 'map', 'Option'] - for builtin in builtins { - typ := v.table.typesmap[builtin] - builtin_types << typ - } - // everything except builtin will get sorted - for t_name, t in v.table.typesmap { - if t_name in builtins || t.is_generic { - continue - } - types << t - } - // sort structs - types_sorted := sort_structs(types) - // Generate C code - res := types_to_c(builtin_types, v.table) + '\n//----\n' + types_to_c(types_sorted, v.table) - return res -} - -// sort structs by dependant fields -fn sort_structs(types []Type) []Type { - mut dep_graph := new_dep_graph() - // types name list - mut type_names := []string - for typ in types { - type_names << typ.name - } - // loop over types - for t in types { - // create list of deps - mut field_deps := []string - for field in t.fields { - // Need to handle fixed size arrays as well (`[10]Point`) - ft := if field.typ.starts_with('[') { field.typ.all_after(']') } else { field.typ } - // skip if not in types list or already in deps - if !(ft in type_names) || ft in field_deps { - continue - } - field_deps << ft // field.typ - } - // add type and dependant types to graph - dep_graph.add(t.name, field_deps) - } - // sort graph - dep_graph_sorted := dep_graph.resolve() - if !dep_graph_sorted.acyclic { - verror('cgen.sort_structs(): the following structs form a dependency cycle:\n' + dep_graph_sorted.display_cycles() + '\nyou can solve this by making one or both of the dependant struct fields references, eg: field &MyStruct' + '\nif you feel this is an error, please create a new issue here: https://github.com/vlang/v/issues and tag @joe-conigliaro') - } - // sort types - mut types_sorted := []Type - for node in dep_graph_sorted.nodes { - for t in types { - if t.name == node.name { - types_sorted << t - continue - } - } - } - return types_sorted -} - -// Generates interface table and interface indexes -fn (v &V) interface_table() string { - mut sb := strings.new_builder(100) - for _, t in v.table.typesmap { - if t.cat != .interface_ { - continue - } - // interface_name is for example Speaker - interface_name := t.name - mut methods := '' - mut generated_casting_functions := '' - sb.writeln('// NR methods = $t.gen_types.len') - for i, gen_type in t.gen_types { - // ptr_ctype can be for example Cat OR Cat_ptr: - ptr_ctype := gen_type.replace('*', '_ptr') - // cctype is the Cleaned Concrete Type name, *without ptr*, - // i.e. cctype is always just Cat, not Cat_ptr: - cctype := gen_type.replace('*', '') - - // Speaker_Cat_index = 0 - interface_index_name := '_${interface_name}_${ptr_ctype}_index' - - generated_casting_functions += ' -${interface_name} I_${cctype}_to_${interface_name}(${cctype} x) { - return (${interface_name}){ - ._object = (void*) memdup(&x, sizeof(${cctype})), - ._interface_idx = ${interface_index_name} }; -} -' - methods += '{\n' - for j, method in t.methods { - // Cat_speak - methods += ' (void*) ${cctype}_${method.name}' - if j < t.methods.len - 1 { - methods += ', \n' - } - } - methods += '\n},\n\n' - sb.writeln('int ${interface_index_name} = $i;') - } - if t.gen_types.len > 0 { - // methods = '{TCCSKIP(0)}' - // } - sb.writeln('void* (* ${interface_name}_name_table[][$t.methods.len]) = ' + '{ \n $methods \n }; ') - } - else { - // The line below is needed so that C compilation succeeds, - // even if no interface methods are called. - // See https://github.com/zenith391/vgtk3/issues/7 - sb.writeln('void* (* ${interface_name}_name_table[][1]) = ' + '{ {NULL} }; ') - } - if generated_casting_functions.len > 0 { - sb.writeln('// Casting functions for interface "${interface_name}" :') - sb.writeln( generated_casting_functions ) - } - } - return sb.str() -} diff --git a/vlib/compiler/cheaders.v b/vlib/compiler/cheaders.v deleted file mode 100644 index 6d93c8b784..0000000000 --- a/vlib/compiler/cheaders.v +++ /dev/null @@ -1,344 +0,0 @@ -module compiler - -const ( - c_common_macros = ' - -#define EMPTY_STRUCT_DECLARATION -#define EMPTY_STRUCT_INITIALIZATION 0 -// Due to a tcc bug, the length of an array needs to be specified, but GCC crashes if it is... -#define EMPTY_ARRAY_OF_ELEMS(x,n) (x[]) -#define TCCSKIP(x) x - -#ifdef __TINYC__ -#undef EMPTY_STRUCT_DECLARATION -#undef EMPTY_STRUCT_INITIALIZATION -#define EMPTY_STRUCT_DECLARATION char _dummy -#define EMPTY_STRUCT_INITIALIZATION 0 -#undef EMPTY_ARRAY_OF_ELEMS -#define EMPTY_ARRAY_OF_ELEMS(x,n) (x[n]) -#undef TCCSKIP -#define TCCSKIP(x) -#endif - -// for __offset_of -#ifndef __offsetof -#define __offsetof(s,memb) \\ - ((size_t)((char *)&((s *)0)->memb - (char *)0)) -#endif - -#define OPTION_CAST(x) (x) - -#ifndef V64_PRINTFORMAT -#ifdef PRIx64 -#define V64_PRINTFORMAT "0x%"PRIx64 -#elif defined(__WIN32__) -#define V64_PRINTFORMAT "0x%I64x" -#elif defined(__LINUX__) && defined(__LP64__) -#define V64_PRINTFORMAT "0x%lx" -#else -#define V64_PRINTFORMAT "0x%llx" -#endif -#endif - -' - c_headers = ' - -//#include // int64_t etc -#include // TODO remove all these includes, define all function signatures and types manually -#include - -//#include "fns.h" -#include -#include // for va_list -#include // memcpy - -#if INTPTR_MAX == INT32_MAX - #define TARGET_IS_32BIT 1 -#elif INTPTR_MAX == INT64_MAX - #define TARGET_IS_64BIT 1 -#else - #error "The environment is not 32 or 64-bit." -#endif - -#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ || defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || defined(__BIG_ENDIAN__) || defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) - #define TARGET_ORDER_IS_BIG -#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ || defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || defined(_M_AMD64) || defined(_M_X64) || defined(_M_IX86) - #define TARGET_ORDER_IS_LITTLE -#else - #error "Unknown architecture endianness" -#endif - -#ifndef _WIN32 -#include -#include // tolower -#include -#include // sleep -extern char **environ; -#endif - -#if defined(__CYGWIN__) && !defined(_WIN32) -#error Cygwin is not supported, please use MinGW or Visual Studio. -#endif - - -#ifdef __linux__ -#include -#include // os__wait uses wait on nix -#endif - -#ifdef __FreeBSD__ -#include -#include // os__wait uses wait on nix -#endif - -#ifdef __DragonFly__ -#include -#include // os__wait uses wait on nix -#endif - -#ifdef __OpenBSD__ -#include -#include -#include // os__wait uses wait on nix -#endif - -#ifdef __NetBSD__ -#include // os__wait uses wait on nix -#endif - -#ifdef __sun -#include -#include // os__wait uses wait on nix -#endif - -$c_common_macros - -#ifdef _WIN32 -#define WINVER 0x0600 -#ifdef _WIN32_WINNT -#undef _WIN32_WINNT -#endif -#define _WIN32_WINNT 0x0600 -#define WIN32_LEAN_AND_MEAN -#define _UNICODE -#define UNICODE -#include - -#include // _waccess -#include // _wgetcwd -//#include -#ifdef _MSC_VER - -// On MSVC these are the same (as long as /volatile:ms is passed) -#define _Atomic volatile - -// MSVC cannot parse some things properly -#undef EMPTY_STRUCT_DECLARATION -#undef OPTION_CAST - -#define EMPTY_STRUCT_DECLARATION int ____dummy_variable -#define OPTION_CAST(x) - -#include -#pragma comment(lib, "Dbghelp.lib") - -extern wchar_t **_wenviron; - -#endif - -#else -#include -#endif - - -//============================== HELPER C MACROS =============================*/ -#define _PUSH(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array_push(arr, &tmp);} -#define _PUSH_MANY(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array_push_many(arr, tmp.data, tmp.len);} -#define _IN(typ, val, arr) array_##typ##_contains(arr, val) -#define _IN_MAP(val, m) map_exists(m, val) -#define DEFAULT_EQUAL(a, b) (a == b) -#define DEFAULT_NOT_EQUAL(a, b) (a != b) -#define DEFAULT_LT(a, b) (a < b) -#define DEFAULT_LE(a, b) (a <= b) -#define DEFAULT_GT(a, b) (a > b) -#define DEFAULT_GE(a, b) (a >= b) - -// NB: macro_fXX_eq and macro_fXX_ne are NOT used -// in the generated C code. They are here just for -// completeness/testing. - -#define macro_f64_eq(a, b) (a == b) -#define macro_f64_ne(a, b) (a != b) -#define macro_f64_lt(a, b) (a < b) -#define macro_f64_le(a, b) (a <= b) -#define macro_f64_gt(a, b) (a > b) -#define macro_f64_ge(a, b) (a >= b) - -#define macro_f32_eq(a, b) (a == b) -#define macro_f32_ne(a, b) (a != b) -#define macro_f32_lt(a, b) (a < b) -#define macro_f32_le(a, b) (a <= b) -#define macro_f32_gt(a, b) (a > b) -#define macro_f32_ge(a, b) (a >= b) - -//================================== GLOBALS =================================*/ -byte g_str_buf[1024]; -int load_so(byteptr); -void reload_so(); - -// ============== wyhash ============== -// Author: Wang Yi -#ifndef wyhash_version_4 -#define wyhash_version_4 -#include -#include -#if defined(_MSC_VER) && defined(_M_X64) -#include -#pragma intrinsic(_umul128) -#endif -const uint64_t _wyp0=0xa0761d6478bd642full, _wyp1=0xe7037ed1a0b428dbull, _wyp2=0x8ebc6af09c88c6e3ull, _wyp3=0x589965cc75374cc3ull, _wyp4=0x1d8e4e27c47d124full; -static inline uint64_t _wyrotr(uint64_t v, unsigned k) { return (v>>k)|(v<<(64-k)); } -static inline uint64_t _wymum(uint64_t A, uint64_t B) { -#ifdef WYHASH32 - uint64_t hh=(A>>32)*(B>>32), hl=(A>>32)*(unsigned)B, lh=(unsigned)A*(B>>32), ll=(uint64_t)(unsigned)A*(unsigned)B; - return _wyrotr(hl,32)^_wyrotr(lh,32)^hh^ll; -#else - #ifdef __SIZEOF_INT128__ - __uint128_t r=A; r*=B; return (r>>64)^r; - #elif defined(_MSC_VER) && defined(_M_X64) - A=_umul128(A, B, &B); return A^B; - #else - uint64_t ha=A>>32, hb=B>>32, la=(uint32_t)A, lb=(uint32_t)B, hi, lo; - uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t>32)+(rm1>>32)+c; return hi^lo; - #endif -#endif -} -#ifndef WYHASH_LITTLE_ENDIAN - #if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) - #define WYHASH_LITTLE_ENDIAN 1 - #elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) - #define WYHASH_LITTLE_ENDIAN 0 - #endif -#endif -#if(WYHASH_LITTLE_ENDIAN) || defined(__TINYC__) -static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v; } -static inline uint64_t _wyr4(const uint8_t *p) { unsigned v; memcpy(&v, p, 4); return v; } -#else - #if defined(__GNUC__) || defined(__INTEL_COMPILER) -static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v); } -static inline uint64_t _wyr4(const uint8_t *p) { unsigned v; memcpy(&v, p, 4); return __builtin_bswap32(v); } - #elif defined(_MSC_VER) -static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);} -static inline uint64_t _wyr4(const uint8_t *p) { unsigned v; memcpy(&v, p, 4); return _byteswap_ulong(v); } - #endif -#endif -static inline uint64_t _wyr3(const uint8_t *p, unsigned k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1]; } -static inline uint64_t wyhash(const void* key, uint64_t len, uint64_t seed) { - const uint8_t *p=(const uint8_t*)key; uint64_t i=len&63; - #if defined(__GNUC__) || defined(__INTEL_COMPILER) - #define _like_(x) __builtin_expect(x,1) - #define _unlike_(x) __builtin_expect(x,0) - #else - #define _like_(x) (x) - #define _unlike_(x) (x) - #endif - if(_unlike_(!i)) { } - else if(_unlike_(i<4)) seed=_wymum(_wyr3(p,i)^seed^_wyp0,seed^_wyp1); - else if(_like_(i<=8)) seed=_wymum(_wyr4(p)^seed^_wyp0,_wyr4(p+i-4)^seed^_wyp1); - else if(_like_(i<=16)) seed=_wymum(_wyr8(p)^seed^_wyp0,_wyr8(p+i-8)^seed^_wyp1); - else if(_like_(i<=24)) seed=_wymum(_wyr8(p)^seed^_wyp0,_wyr8(p+8)^seed^_wyp1)^_wymum(_wyr8(p+i-8)^seed^_wyp2,seed^_wyp3); - else if(_like_(i<=32)) seed=_wymum(_wyr8(p)^seed^_wyp0,_wyr8(p+8)^seed^_wyp1)^_wymum(_wyr8(p+16)^seed^_wyp2,_wyr8(p+i-8)^seed^_wyp3); - else{ seed=_wymum(_wyr8(p)^seed^_wyp0,_wyr8(p+8)^seed^_wyp1)^_wymum(_wyr8(p+16)^seed^_wyp2,_wyr8(p+24)^seed^_wyp3)^_wymum(_wyr8(p+i-32)^seed^_wyp1,_wyr8(p+i-24)^seed^_wyp2)^_wymum(_wyr8(p+i-16)^seed^_wyp3,_wyr8(p+i-8)^seed^_wyp0); } - if(_like_(i==len)) return _wymum(seed,len^_wyp4); - uint64_t see1=seed, see2=seed, see3=seed; - for(p+=i,i=len-i; _like_(i>=64); i-=64,p+=64) { - seed=_wymum(_wyr8(p)^seed^_wyp0,_wyr8(p+8)^seed^_wyp1); see1=_wymum(_wyr8(p+16)^see1^_wyp2,_wyr8(p+24)^see1^_wyp3); - see2=_wymum(_wyr8(p+32)^see2^_wyp1,_wyr8(p+40)^see2^_wyp2); see3=_wymum(_wyr8(p+48)^see3^_wyp3,_wyr8(p+56)^see3^_wyp0); - } - return _wymum(seed^see1^see2,see3^len^_wyp4); -} -static inline uint64_t wyhash64(uint64_t A, uint64_t B) { return _wymum(_wymum(A^_wyp0, B^_wyp1), _wyp2); } -static inline uint64_t wyrand(uint64_t *seed) { *seed+=_wyp0; return _wymum(*seed^_wyp1,*seed); } -static inline double wy2u01(uint64_t r) { const double _wynorm=1.0/(1ull<<52); return (r>>11)*_wynorm; } -static inline double wy2gau(uint64_t r) { const double _wynorm=1.0/(1ull<<20); return ((r&0x1fffff)+((r>>21)&0x1fffff)+((r>>42)&0x1fffff))*_wynorm-3.0; } -static inline uint64_t fastest_hash(const void *key, size_t len, uint64_t seed) { - const uint8_t *p = (const uint8_t *)key; - return _like_(len >= 4) ? (_wyr4(p) + _wyr4(p + len - 4)) * (_wyr4(p + (len >> 1) - 2) ^ seed) : (_like_(len) ? _wyr3(p, len) * (_wyp0 ^ seed) : seed); -} -#endif - -' - js_headers = ' - -var array_string = function() {} -var array_byte = function() {} -var array_int = function() {} -var byte = function() {} -var double = function() {} -var int = function() {} -var f64 = function() {} -var f32 = function() {} -var i64 = function() {} -var i32 = function() {} -var i16 = function() {} -var u64 = function() {} -var u32 = function() {} -var u16 = function() {} -var i8 = function() {} -var bool = function() {} -var rune = function() {} -var map_string = function() {} -var map_int = function() {} - -' - c_builtin_types = ' - -//#include // int64_t etc -//#include // int64_t etc - -//================================== 1TYPEDEFS ================================*/ - -typedef int64_t i64; -typedef int16_t i16; -typedef int8_t i8; -typedef uint64_t u64; -typedef uint32_t u32; -typedef uint16_t u16; -typedef uint8_t byte; -typedef uint32_t rune; -typedef float f32; -typedef double f64; -typedef unsigned char* byteptr; -typedef int* intptr; -typedef void* voidptr; -typedef char* charptr; -typedef struct array array; -typedef struct map map; -typedef array array_string; -typedef array array_int; -typedef array array_byte; -typedef array array_f32; -typedef array array_f64; -typedef array array_u16; -typedef array array_u32; -typedef array array_u64; -typedef map map_int; -typedef map map_string; -#ifndef bool - typedef int bool; - #define true 1 - #define false 0 -#endif -' - bare_c_headers = ' - -$c_common_macros - -#ifndef exit -#define exit(rc) sys_exit(rc) -void sys_exit (int); -#endif - -' -) diff --git a/vlib/compiler/compile_errors.v b/vlib/compiler/compile_errors.v deleted file mode 100644 index e7738a2e35..0000000000 --- a/vlib/compiler/compile_errors.v +++ /dev/null @@ -1,317 +0,0 @@ -module compiler - -import ( - os - term -) -// //////////////////////////////////////////////////////////////////////////////////////////////// -// NB: The code in this file is organized in layers (between the ///// lines). -// This allows for easier keeping in sync of error/warn functions. -// The functions in each of the layers, call the functions from the layers *below*. -// The functions in each of the layers, also have more details about the warn/error situation, -// so they can display more informative message, so please call the lowest level variant you can. -// //////////////////////////////////////////////////////////////////////////////////////////////// -// TLDR: If you have a token index, call: -// p.error_with_token_index(msg, token_index) -// ... not just : -// p.error(msg) -// //////////////////////////////////////////////////////////////////////////////////////////////// - - -fn (p mut Parser) error(s string) { - // no positioning info, so just assume that the last token was the culprit: - p.error_with_token_index(s, p.token_idx - 1) -} - -fn (p mut Parser) warn_or_error(s string) { - if p.pref.is_prod { - p.error(s) - } else { - p.warn(s) - } -} - -fn (p mut Parser) warn(s string) { - p.warn_with_token_index(s, p.token_idx - 1) -} - -fn (p mut Parser) production_error_with_token_index(e string, tokenindex int) { - if p.pref.is_prod { - p.error_with_token_index(e, tokenindex) - } - else { - p.warn_with_token_index(e, tokenindex) - } -} - -fn (p mut Parser) error_with_token_index(s string, tokenindex int) { - p.error_with_position(s, p.scanner.get_scanner_pos_of_token(p.tokens[tokenindex])) -} - -fn (p mut Parser) warn_with_token_index(s string, tokenindex int) { - p.warn_with_position(s, p.scanner.get_scanner_pos_of_token(p.tokens[tokenindex])) -} - -fn (p mut Parser) error_with_position(s string, sp ScannerPos) { - p.print_error_context() - e := normalized_error(s) - p.scanner.goto_scanner_position(sp) - p.scanner.error_with_col(e, sp.pos - sp.last_nl_pos) -} - -fn (p mut Parser) warn_with_position(s string, sp ScannerPos) { - if p.scanner.is_fmt { - return - } - // on a warning, restore the scanner state after printing the warning: - cpos := p.scanner.get_scanner_pos() - e := normalized_error(s) - p.scanner.goto_scanner_position(sp) - p.scanner.warn_with_col(e, sp.pos - sp.last_nl_pos) - p.scanner.goto_scanner_position(cpos) -} - -fn (s &Scanner) error(msg string) { - s.error_with_col(msg, 0) -} - -fn (s &Scanner) warn(msg string) { - s.warn_with_col(msg, 0) -} - -fn (s &Scanner) warn_with_col(msg string, col int) { - fullpath := s.get_error_filepath() - color_on := s.is_color_output_on() - final_message := if color_on { term.bold(term.bright_blue(msg)) } else { msg } - eprintln('warning: ${fullpath}:${s.line_nr+1}:${col}: $final_message') -} - -fn (s &Scanner) error_with_col(msg string, col int) { - fullpath := s.get_error_filepath() - color_on := s.is_color_output_on() - final_message := if color_on { term.red(term.bold(msg)) } else { msg } - // 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. - // NB: using only the filename may lead to inability of IDE/editors - // to find the source file, when the IDE has a different working folder than v itself. - eprintln('${fullpath}:${s.line_nr + 1}:${col}: $final_message') - if s.print_line_on_error && s.nlines > 0 { - context_start_line := imax(0, (s.line_nr - error_context_before)) - context_end_line := imin(s.nlines - 1, (s.line_nr + error_context_after + 1)) - for cline := context_start_line; cline < context_end_line; cline++ { - line := '${(cline+1):5d}| ' + s.line(cline) - coloredline := if cline == s.line_nr && color_on { term.red(line) } else { line } - eprintln(coloredline) - if cline != s.line_nr { - continue - } - // The pointerline should have the same spaces/tabs as the offending - // line, so that it prints the ^ character exactly on the *same spot* - // where it is needed. That is the reason we can not just - // use strings.repeat(` `, col) to form it. - mut pointerline := []string - for i, c in line { - if i < col { - x := if c.is_space() { c } else { ` ` } - pointerline << x.str() - continue - } - pointerline << if color_on { term.bold(term.blue('^')) } else { '^' } - break - } - eprintln(' ' + pointerline.join('')) - } - } - exit(1) -} - -// //////////////////////////////////////////////////////////////////////////////////////////////// -// / Misc error helper functions, can be called by any of the functions above -[inline] -fn (p &Parser) cur_tok_index() int { - return p.token_idx - 1 -} - -[inline] -fn imax(a, b int) int { - return if a > b { a } else { b } -} - -[inline] -fn imin(a, b int) int { - return if a < b { a } else { b } -} - -fn (s &Scanner) get_error_filepath() string { - verror_paths_override := os.getenv('VERROR_PATHS') - use_relative_paths := match verror_paths_override { - 'relative'{ - true - } - 'absolute'{ - false - } - else { - s.print_rel_paths_on_error}} - if use_relative_paths { - workdir := os.getwd() + os.path_separator - if s.file_path.starts_with(workdir) { - return s.file_path.replace(workdir, '') - } - return s.file_path - } - return os.real_path(s.file_path) -} - -fn (s &Scanner) is_color_output_on() bool { - return s.print_colored_error && term.can_show_color_on_stderr() -} - -fn (p mut Parser) print_error_context() { - // Dump all vars and types for debugging - if p.pref.is_debug { - // os.write_to_file('/var/tmp/lang.types', '')//pes(p.table.types)) - os.write_file('fns.txt', p.table.debug_fns()) - } - if p.pref.verbosity.is_higher_or_equal(.level_three) { - println('pass=$p.pass fn=`$p.cur_fn.name`\n') - } - p.cgen.save() - // V up hint - cur_path := os.getwd() - if !p.pref.is_repl && !p.pref.is_test && (p.file_path.contains('v/compiler') || cur_path.contains('v/compiler')) { - println('\n=========================') - println('It looks like you are building V. It is being frequently updated every day.') - println("If you didn\'t modify V\'s code, most likely there was a change that ") - println('lead to this error.') - println('\nRun `v up`, that will most likely fix it.') - // println('\nIf this doesn\'t help, re-install V from source or download a precompiled' + ' binary from\nhttps://vlang.io.') - println("\nIf this doesn\'t help, please create a GitHub issue.") - println('=========================\n') - } - if p.pref.is_debug { - print_backtrace() - } - // p.scanner.debug_tokens() -} - -fn ienv_default(ename string, idefault int) int { - es := os.getenv(ename) - if es.len == 0 { return idefault } - return es.int() -} - -// print_current_tokens/1 pretty prints the current token context, like this: -// // Your label: tokens[ 32] = Token{ .line: 8, .pos: 93, .tok: 85 } = mut -// // Your label: tokens[> 33] = Token{ .line: 8, .pos: 95, .tok: 1 } = b -// // Your label: tokens[ 34] = Token{ .line: 8, .pos: 98, .tok: 31 } = := -// It is useful while debugging the v compiler itself. > marks p.token_idx -fn (p &Parser) print_current_tokens(label string){ - btokens := ienv_default('V_BTOKENS', 5) - atokens := ienv_default('V_ATOKENS', 5) - ctoken_idx := p.token_idx - stoken_idx := imax(0, ctoken_idx - btokens) - etoken_idx := imin( ctoken_idx + atokens + 1, p.tokens.len) - for i := stoken_idx; i < etoken_idx; i++ { - idx := if i == ctoken_idx { - '>${i:3d}' - } else { - ' ${i:3d}' - } - eprintln('$label: tokens[$idx] = ' + p.tokens[ i ].detailed_str()) - } -} - -fn normalized_error(s string) string { - mut res := s - if !res.contains('__') { - // `[]int` instead of `array_int` - res = res.replace('array_', '[]') - } - res = res.replace('__', '.') - res = res.replace('Option_', '?') - res = res.replace('main.', '') - res = res.replace('ptr_', '&') - res = res.replace('_dot_', '.') - if res.contains('_V_MulRet_') { - res = res.replace('_V_MulRet_', '(') - res = res.replace('_V_', ', ') - res = res[..res.len - 1] + ')"' //"// quote balance comment. do not remove - } - return res -} - -// //////////////////////////////////////////////////////////////////////////////////////////////// -// The goal of ScannerPos is to track the current scanning position, -// so that if there is an error found later, v could show a more accurate -// position about where the error initially was. -// NB: The fields of ScannerPos *should be kept synchronized* with the -// corresponding fields in Scanner. -struct ScannerPos { -mut: - pos int - line_nr int - last_nl_pos int -} - -pub fn (s ScannerPos) str() string { - return 'ScannerPos{ ${s.pos:5d} , ${s.line_nr:5d} , ${s.last_nl_pos:5d} }' -} - -fn (s &Scanner) get_scanner_pos() ScannerPos { - return ScannerPos{ - pos: s.pos - line_nr: s.line_nr - last_nl_pos: s.last_nl_pos - } -} - -fn (s mut Scanner) goto_scanner_position(scp ScannerPos) { - s.pos = scp.pos - s.line_nr = scp.line_nr - s.last_nl_pos = scp.last_nl_pos -} - -fn (s &Scanner) get_last_nl_from_pos(_pos int) int { - pos := if _pos >= s.text.len { s.text.len - 1 } else { _pos } - for i := pos; i >= 0; i-- { - if s.text[i] == `\n` { - return i - } - } - return 0 -} - -fn (s &Scanner) get_scanner_pos_of_token(tok &Token) ScannerPos { - return ScannerPos{ - pos: tok.pos - line_nr: tok.line_nr - last_nl_pos: s.get_last_nl_from_pos(tok.pos) - } -} - -// ///////////////////////////// -fn (p mut Parser) mutable_arg_error(i int, arg Var, f Fn) { - mut dots_example := 'mut $p.lit' - if i > 0 { - dots_example = '.., ' + dots_example - } - if i < f.args.len - 1 { - dots_example = dots_example + ',..' - } - p.error('`$arg.name` is a mutable argument, you need to provide `mut`: ' + '`$f.name ($dots_example)`') -} - -const ( - warn_match_arrow = '=> is no longer needed in match statements, use\n' + 'match foo { - 1 { bar } - 2 { baz } - else { ... } -}' - // make_receiver_mutable = - err_used_as_value = 'used as value' - and_or_error = 'use `()` to make the boolean expression clear\n' + 'for example: `(a && b) || c` instead of `a && b || c`' - err_modify_bitfield = 'to modify a bitfield flag use the methods: set, clear, toggle. and to check for flag use: has' -) diff --git a/vlib/compiler/compiler_options.v b/vlib/compiler/compiler_options.v deleted file mode 100644 index 7e09da7629..0000000000 --- a/vlib/compiler/compiler_options.v +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -pub fn get_v_options_and_main_command(args []string) ([]string,string) { - mut options := []string - mut potential_commands := []string - for i := 0; i < args.len; i++ { - a := args[i] - if !a.starts_with('-') { - potential_commands << a - continue - } - else { - options << a - if a in ['-o', '-os', '-cc', '-cflags', '-d'] { - i++ - } - } - } - // potential_commands[0] is always the executable itself, so ignore it - command := if potential_commands.len > 1 { potential_commands[1] } else { '' } - return options,command -} - diff --git a/vlib/compiler/comptime.v b/vlib/compiler/comptime.v deleted file mode 100644 index 2c16852db5..0000000000 --- a/vlib/compiler/comptime.v +++ /dev/null @@ -1,612 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import ( - vweb.tmpl // for `$vweb_html()` - os - strings -) - -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 supported_platforms { - os := os_from_string(name) - ifdef_name := os_name_to_ifdef(name) - if name == 'mac' { - p.warn('use `macos` instead of `mac`') - } - - if not { - if name == 'linux_or_macos' { - p.genln('#if !defined(__linux__) && !defined(__APPLE__)') - } else { - p.genln('#ifndef $ifdef_name') - } - } - else { - if name == 'linux_or_macos' { - p.genln('#if defined(__linux__) || defined(__APPLE__)') - } else { - p.genln('#ifdef $ifdef_name') - } - } - - p.check(.lcbr) - if ((!not && os != p.os) || (not && os == p.os)) && !name.contains('_or_') && - !p.scanner.is_fmt && !p.pref.output_cross_c { - // `$if os {` for a different target, skip everything inside - // to avoid compilation errors (like including - // on non-Windows systems) - mut stack := 1 - for { - if p.tok == .key_return { - p.returns = true - } - if p.tok == .lcbr { - stack++ - } - else if p.tok == .rcbr { - stack-- - } - if p.tok == .eof { - break - } - if stack <= 0 && p.tok == .rcbr { - // p.warn('exiting $stack') - p.next() - break - } - p.next() - } - } - else { - p.statements_no_rcbr() - } - if !(p.tok == .dollar && p.peek() == .key_else) { - p.genln('#endif') - } - } - else if name == 'x64' { - p.comptime_if_block('TARGET_IS_64BIT', not) - } - else if name == 'x32' { - p.comptime_if_block('TARGET_IS_32BIT', not) - } - else if name == 'big_endian' { - p.comptime_if_block('TARGET_ORDER_IS_BIG', not) - } - else if name == 'little_endian' { - p.comptime_if_block('TARGET_ORDER_IS_LITTLE', not) - } - else if name == 'debug' { - p.comptime_if_block('VDEBUG', not) - } - else if name == 'prealloc' { - p.comptime_if_block('VPREALLOC', not) - } - else if name == 'tinyc' { - p.comptime_if_block('__TINYC__', not) - } - else if name == 'glibc' { - p.comptime_if_block('__GLIBC__', not) - } - else if name == 'mingw' { - p.comptime_if_block('__MINGW32__', not) - } - else if name == 'msvc' { - p.comptime_if_block('_MSC_VER', not) - } - else if name == 'clang' { - p.comptime_if_block('__clang__', not) - } - else if p.v.pref.compile_defines_all.len > 0 && name in p.v.pref.compile_defines_all { - // Support for *optional* custom compile defines, i.e.: - // - // `[if custom]` => custom should be defined - // `$if custom { // stuff }` => custom should be defined - // `$if custom ? { // stuff }` => custom may not be defined - // - // Custom compile defines are given on the CLI, like this: - // `v -d custom=0` => means that the custom will be defined, - // but that it will be considered false. - // `v -d custom=1`, which is equivalent to `v -d custom`, - // means that the custom will be defined, and considered true. - // - // The ? sign, means that `custom` is optional, and when - // it is not present at all at the command line, then the - // block will just be ignored, instead of erroring. - if p.tok == .question { - p.next() - } - p.comptime_if_block('CUSTOM_DEFINE_${name}', not) - } else { - if p.tok == .question { - p.next() - p.comptime_if_block('CUSTOM_DEFINE_${name}', not) - }else{ - println('Supported platforms:') - println(supported_platforms) - p.error('unknown platform `$name`') - } - } - if_returns := p.returns - p.returns = false - // p.gen('/* returns $p.returns */') - if p.tok == .dollar && p.peek() == .key_else { - p.fspace() - p.next() - p.next() - p.fspace() // spaces before and after $else - p.check(.lcbr) - p.genln('#else') - p.statements_no_rcbr() - p.genln('#endif') - else_returns := p.returns - p.returns = if_returns && else_returns - // p.gen('/* returns $p.returns */') - } - else if p.tok == .key_else { - p.error('use `$' + 'else` instead of `else` in comptime if statements') - } - } - 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) - _,val := p.tmp_expr() - // p.bool_expression() - // val := p.cgen.end_tmp() - p.check(.rcbr) - // } - } - else if p.tok == .name && p.lit == 'vweb' { - // $vweb.html() - // Compile vweb html template to V code, parse that V code and embed the resulting V functions - // that returns an html string - mut path := p.cur_fn.name + '.html' - if p.pref.is_debug { - println('>>> compiling vweb HTML template "$path"') - } - if !os.exists(path) { - // Can't find the template file in current directory, - // try looking next to the vweb program, in case it's run with - // v path/to/vweb_app.v - path = os.dir(p.scanner.file_path) + '/' + path - if !os.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) - if p.pref.verbosity.is_higher_or_equal(.level_three) { - println('\n\n') - println('>>> vweb template for ${path}:') - println(v_code) - println('>>> vweb template END') - println('\n\n') - } - is_strings_imorted := p.import_table.known_import('strings') - if !is_strings_imorted { - p.register_import('strings', 0) // used by v_code - } - p.import_table.register_used_import('strings') - p.genln('/////////////////// tmpl start') - p.statements_from_text(v_code, false, path) - p.genln('/////////////////// tmpl end') - receiver := p.cur_fn.args[0] - dot := if receiver.is_mut || receiver.ptr || receiver.typ.ends_with('*') { '->' } else { '.' } - p.genln('vweb__Context_html( & $receiver.name /*!*/$dot vweb, tmpl_res)') - } - else { - p.error('bad comp_time expression') - } -} - -// #include, #flag, #v -fn (p mut Parser) chash() { - hash := p.lit.trim_space() - // println('chsh() file=$p.file hash="$hash"') - p.next() - p.fgen_nl() - if hash.starts_with('flag ') { - if p.first_pass() { - mut flag := hash[5..] - // expand `@VROOT` to its absolute path - if flag.contains('@VROOT') { - vmod_file_location := p.v.mod_file_cacher.get( p.file_path_dir ) - if vmod_file_location.vmod_file.len == 0 { - // There was no actual v.mod file found. - p.error_with_token_index('To use @VROOT, you need' + - ' to have a "v.mod" file in ${p.file_path_dir},' + - ' or in one of its parent folders.', - p.cur_tok_index() - 1) - } - flag = flag.replace('@VROOT', vmod_file_location.vmod_folder ) - } - for deprecated in ['@VMOD', '@VMODULE', '@VPATH', '@VLIB_PATH'] { - if flag.contains(deprecated) { - p.error('${deprecated} had been deprecated, use @VROOT instead.') - } - } - // p.log('adding flag "$flag"') - _ = p.table.parse_cflag(flag, p.mod, p.v.pref.compile_defines_all ) or { - p.error_with_token_index(err, p.cur_tok_index() - 1) - return - } - } - return - } - if hash.starts_with('include') { - if p.first_pass() && !p.is_vh { - /* - if !p.pref.building_v && !p.fileis('vlib') { - p.warn('C #includes will soon be removed from the language' + - '\ndefine the C structs and functions in V') - } - */ - if p.file_pcguard.len != 0 { - // println('p: $p.file_platform $p.file_pcguard') - p.cgen.includes << '$p.file_pcguard\n#$hash\n#endif' - return - } - p.cgen.includes << '#$hash' - return - } - } - // TODO remove after ui_mac.m is removed - else if hash.contains('embed') { - pos := hash.index('embed') or { - return - } - file := hash[pos + 5..] - // if p.pref.build_mode != .default_mode { - p.genln('#include $file') - // } - } - else if hash.contains('define') { - // Move defines on top - if p.first_pass() { - p.cgen.includes << '#$hash' - } - } - // // Don't parse a non-JS V file (`#-js` flag) - else if hash == '-js' { - $if js { - for p.tok != .eof { - p.next() - } - } $else { - p.next() - } - } - else { - $if !js { - if !p.can_chash { - println('hash="$hash"') - if hash.starts_with('include') { - println('include') - } - else { - } - 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() - mut j := 0 - for method in typ.methods { - if method.typ != 'void' { - continue - } - receiver := method.args[0] - if !p.expr_var.ptr { - p.error('`$p.expr_var.name` needs to be a reference') - } - amp := if receiver.is_mut && !p.expr_var.ptr { '&' } else { '' } - if j > 0 { - p.gen(' else ') - } - p.genln('if ( string_eq($var, _STR("$method.name")) ) ' + '${typ.name}_$method.name ($amp $p.expr_var.name);') - j++ - } - p.check(.lpar) - p.check(.rpar) - if p.tok == .key_orelse { - p.check(.key_orelse) - p.genln('else {') - p.check(.lcbr) - p.statements() - } -} - -fn (p mut Parser) gen_default_str_method_if_missing(typename string) (bool, string) { - // NB: string_type_name can be != typename, if the base typename has str() - mut string_type_name := typename - typ := p.table.find_type(typename) - is_varg := typename.starts_with('varg_') - is_array := typename.starts_with('array_') - is_struct := typ.cat == .struct_ - mut has_str_method := p.table.type_has_method(typ, 'str') - if !has_str_method { - if is_varg { - p.gen_varg_str(typ) - has_str_method = true - } - else if is_array { - p.gen_array_str(typ) - has_str_method = true - } - else if is_struct { - p.gen_struct_str(typ) - has_str_method = true - } - else { - btypename := p.base_type(typ.name) - if btypename != typ.name { - base_type := p.find_type(btypename) - if base_type.has_method('str'){ - string_type_name = base_type.name - has_str_method = true - } - } - } - } - return has_str_method, string_type_name -} - -fn (p mut Parser) gen_array_str(typ Type) { - if typ.has_method('str') { - return - } - p.add_method(typ.name, Fn{ - name: 'str' - typ: 'string' - args: [Var{ - typ: typ.name - is_arg: true - }] - is_method: true - is_public: true - receiver_typ: typ.name - }) - elm_type := parse_pointer(typ.name[6..]) - elm_type2 := p.table.find_type(elm_type) - is_array := elm_type.starts_with('array_') - if is_array { - p.gen_array_str(elm_type2) - } - else if p.typ_to_fmt(elm_type, 0) == '' && !p.table.type_has_method(elm_type2, 'str') { - has_str_method, _ := p.gen_default_str_method_if_missing( elm_type ) - if !has_str_method { - p.error('cant print []${elm_type}, unhandled print of ${elm_type}') - } - } - p.v.vgen_buf.writeln(' -pub fn (a $typ.name) str() string { - mut sb := strings.new_builder(a.len * 3) - sb.write("[") - for i, elm in a { - sb.write(elm.str()) - if i < a.len - 1 { - sb.write(", ") - } - } - sb.write("]") - return sb.str() -} - ') - p.cgen.fns << 'string ${typ.name}_str();' -} - -// `Foo { bar: 3, baz: 'hi' }` => interpolated to string 'Foo { bar: 3, baz: "hi" }' -fn (p mut Parser) gen_struct_str(typ Type) { - p.add_method(typ.name, Fn{ - name: 'str' - typ: 'string' - args: [Var{ - typ: typ.name - is_arg: true - }] - is_method: true - is_public: true - receiver_typ: typ.name - }) - mut sb := strings.new_builder(typ.fields.len * 20) - sb.writeln('pub fn (a $typ.name) str() string {\nreturn') - short_struct_name := typ.name.all_after('__') - sb.writeln("'$short_struct_name {") - for field in typ.fields { - sb.writeln('\t$field.name: $' + 'a.${field.name}') - } - sb.writeln("}'") - sb.writeln('}') - p.v.vgen_buf.writeln(sb.str()) - // Need to manually add the definition to `fns` so that it stays - // at the top of the file. - // This function will get parsed by V after the main pass. - p.cgen.fns << 'string ${typ.name}_str();' -} - -fn (p mut Parser) gen_varg_str(typ Type) { - elm_type := typ.name[5..] - elm_type2 := p.table.find_type(elm_type) - is_array := elm_type.starts_with('array_') - if is_array { - p.gen_array_str(elm_type2) - } - else if elm_type2.cat == .struct_ { - p.gen_struct_str(elm_type2) - } - p.v.vgen_buf.writeln(' -pub fn (a $typ.name) str() string { - mut sb := strings.new_builder(a.len * 3) - sb.write("[") - for i, elm in a { - sb.write(elm.str()) - if i < a.len - 1 { - sb.write(", ") - } - } - sb.write("]") - return sb.str() -}') - p.cgen.fns << 'string ${typ.name}_str();' -} - -fn (p mut Parser) gen_array_filter(str_typ string, method_ph int) { - /* - // V - a := [1,2,3,4] - b := a.filter(it % 2 == 0) - - // C - array_int a = ...; - array_int tmp2 = new_array(0, 4, 4); - for (int i = 0; i < a.len; i++) { - int it = ((int*)a.data)[i]; - if (it % 2 == 0) array_push(&tmp2, &it); - } - array_int b = tmp2; - */ - val_type := parse_pointer(str_typ[6..]) - p.open_scope() - p.register_var(Var{ - name: 'it' - typ: val_type - }) - p.next() - p.check(.lpar) - p.cgen.resetln('') - tmp := p.get_tmp() - a := p.expr_var.name - p.cgen.set_placeholder(method_ph, '\n$str_typ $tmp = new_array(0, $a .len,sizeof($val_type));\n') - p.genln('for (int i = 0; i < ${a}.len; i++) {') - p.genln('$val_type it = (($val_type*)${a}.data)[i];') - p.gen('if (') - p.bool_expression() - p.genln(') array_push(&$tmp, &it);') - // p.genln(') array_push(&$tmp, &((($val_type*)${a}.data)[i]));') - // p.genln(') array_push(&$tmp, ${a}.data + i * ${a}.element_size);') - p.genln('}') - p.gen(tmp) // TODO why does this `gen()` work? - p.check(.rpar) - p.close_scope() -} - -fn (p mut Parser) gen_array_map(str_typ string, method_ph int) string { - /* - // V - a := [1,2,3,4] - b := a.map(it * 2) - - // C - array_int a = ...; - array_int tmp2 = new_array(0, 4, 4); - for (int i = 0; i < a.len; i++) { - int it = ((int*)a.data)[i]; - _PUSH(tmp2, it * 2, tmp3, int) - } - array_int b = tmp2; - */ - val_type := parse_pointer(str_typ[6..]) - p.open_scope() - p.register_var(Var{ - name: 'it' - typ: val_type - }) - p.next() - p.check(.lpar) - p.cgen.resetln('') - tmp := p.get_tmp() - tmp_elm := p.get_tmp() - a := p.expr_var.name - map_type,expr := p.tmp_expr() - p.cgen.set_placeholder(method_ph, '\narray $tmp = new_array(0, $a .len, ' + 'sizeof($map_type));\n') - p.genln('for (int i = 0; i < ${a}.len; i++) {') - p.genln('$val_type it = (($val_type*)${a}.data)[i];') - p.genln('_PUSH(&$tmp, $expr, $tmp_elm, $map_type)') - p.genln('}') - p.gen(tmp) // TODO why does this `gen()` work? - p.check(.rpar) - p.close_scope() - return 'array_' + stringify_pointer(map_type) -} - -fn (p mut Parser) comptime_if_block(name string, not bool) { - if not { - p.genln('#ifndef $name') - }else{ - p.genln('#ifdef $name') - } - p.check(.lcbr) - p.statements_no_rcbr() - if !(p.tok == .dollar && p.peek() == .key_else) { - p.genln('#endif') - } -} - -fn (p mut Parser) gen_enum_flag_methods(typ mut Type) { - for method in ['set', 'clear', 'toggle', 'has'] { - typ.methods << Fn{ - name: method - typ: if method == 'has' { 'bool' } else { 'void' } - args: [Var{ - typ: typ.name - is_mut: true - is_arg: true - }, Var{ - typ: typ.name - is_arg: true - }] - is_method: true - is_public: true - receiver_typ: typ.name - } - } - p.v.vgen_buf.writeln(' -pub fn (e mut $typ.name) set(flag $typ.name) { *e = int(*e) | (1 << int(flag)) } -pub fn (e mut $typ.name) clear(flag $typ.name) { *e = int(*e) &~ (1 << int(flag)) } -pub fn (e mut $typ.name) toggle(flag $typ.name) { *e = int(*e) ^ (1 << int(flag)) } -pub fn (e &$typ.name) has(flag $typ.name) bool { return int(*e)&(1 << int(flag)) != 0 }') - p.cgen.fns << 'void ${typ.name}_set($typ.name *e, $typ.name flag);' - p.cgen.fns << 'void ${typ.name}_clear($typ.name *e, $typ.name flag);' - p.cgen.fns << 'void ${typ.name}_toggle($typ.name *e, $typ.name flag);' - p.cgen.fns << 'bool ${typ.name}_has($typ.name *e, $typ.name flag);' -} diff --git a/vlib/compiler/depgraph.v b/vlib/compiler/depgraph.v deleted file mode 100644 index 44a93e6d22..0000000000 --- a/vlib/compiler/depgraph.v +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -// Directed acyclic graph -// this implementation is specifically suited to ordering dependencies -module compiler - -struct DepGraphNode { -mut: - name string - deps []string -} - -struct DepGraph { -pub mut: - acyclic bool - nodes []DepGraphNode -} - -struct OrderedDepMap { -mut: - keys []string - data map[string][]string -} - -pub fn (o mut OrderedDepMap) set(name string, deps []string) { - if !(name in o.data) { - o.keys << name - } - o.data[name] = deps -} - -pub fn (o mut OrderedDepMap) add(name string, deps []string) { - mut d := o.data[name] - for dep in deps { - if !(dep in d) { - d << dep - } - } - o.set(name, d) -} - -pub fn (o &OrderedDepMap) get(name string) []string { - return o.data[name] -} - -pub fn (o mut OrderedDepMap) delete(name string) { - if !(name in o.data) { - panic('delete: no such key: $name') - } - for i, _ in o.keys { - if o.keys[i] == name { - o.keys.delete(i) - break - } - } - o.data.delete(name) -} - -pub fn (o mut OrderedDepMap) apply_diff(name string, deps []string) { - mut diff := []string - for dep in o.data[name] { - if !(dep in deps) { - diff << dep - } - } - o.set(name, diff) -} - -pub fn (o &OrderedDepMap) size() int { - return o.data.size -} - -pub fn new_dep_graph() &DepGraph { - return &DepGraph{ - acyclic: true - } -} - -pub fn (graph mut DepGraph) add(mod string, deps []string) { - graph.nodes << DepGraphNode{ - name: mod - deps: deps.clone() - } -} - -pub fn (graph &DepGraph) resolve() &DepGraph { - mut node_names := OrderedDepMap{} - for node in graph.nodes { - node_names.add(node.name, node.deps) - } - mut node_deps := node_names - mut resolved := new_dep_graph() - for node_deps.size() != 0 { - mut ready_set := []string - for name in node_deps.keys { - deps := node_deps.data[name] - if deps.len == 0 { - ready_set << name - } - } - if ready_set.len == 0 { - mut g := new_dep_graph() - g.acyclic = false - for name in node_deps.keys { - g.add(name, node_names.data[name]) - } - return g - } - for name in ready_set { - node_deps.delete(name) - resolved.add(name, node_names.data[name]) - } - for name in node_deps.keys { - node_deps.apply_diff(name, ready_set) - } - } - return resolved -} - -pub fn (graph &DepGraph) last_node() DepGraphNode { - return graph.nodes[graph.nodes.len - 1] -} - -pub fn (graph &DepGraph) display() string { - mut out := '\n' - for node in graph.nodes { - for dep in node.deps { - out += ' * $node.name -> $dep\n' - } - } - return out -} - -pub fn (graph &DepGraph) display_cycles() string { - mut node_names := map[string]DepGraphNode - for node in graph.nodes { - node_names[node.name] = node - } - mut out := '\n' - for node in graph.nodes { - for dep in node.deps { - if !(dep in node_names) { - continue - } - dn := node_names[dep] - if node.name in dn.deps { - out += ' * $node.name -> $dep\n' - } - } - } - return out -} diff --git a/vlib/compiler/enum.v b/vlib/compiler/enum.v deleted file mode 100644 index 72490ce591..0000000000 --- a/vlib/compiler/enum.v +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -fn (p mut Parser) enum_decl(no_name bool) { - is_pub := p.tok == .key_pub - if is_pub { - p.next() - p.fspace() - } - p.check(.key_enum) - p.fspace() - mut enum_name := p.check_name() - is_c := enum_name == 'C' && p.tok == .dot - if is_c { - p.check(.dot) - enum_name = p.check_name() - } - // Specify full type name - if !p.builtin_mod && p.mod != 'main' { - enum_name = p.prepend_mod(enum_name) - } - p.fspace() - p.check(.lcbr) - mut val := 0 - mut fields := []string - mut tuple_variants := []string - for p.tok == .name { - field := p.check_name() - if p.pass == .decl && p.tok != .lpar && contains_capital(field) { - p.warn('enum values cannot contain uppercase letters, use snake_case instead (`$field`)') - } - fields << field - name := '${mod_gen_name(p.mod)}__${enum_name}_$field' - if p.tok == .assign { - p.fspace() - mut enum_assign_tidx := p.cur_tok_index() - next := p.peek() - if next in [.number, .minus] { - p.next() - p.fspace() - is_neg := p.tok == .minus - if is_neg { - p.next() - } - val = p.lit.int() - if is_neg { - val = -val - } - p.next() - } - else { - p.next() - enum_assign_tidx = p.cur_tok_index() - p.error_with_token_index('only numbers are allowed in enum initializations', enum_assign_tidx) - } - } - // `BoolExpr(bool)` - else if p.tok == .lpar { - if !field[0].is_capital() { - p.error('sum types must be capitalized') - } - p.check(.lpar) - tuple_variants << p.get_type() - p.check(.rpar) - if p.pass == .main { - p.cgen.consts << '#define ${field}_type $val // LOL' - } - } - if p.pass == .main { - p.cgen.consts << '#define $name $val' - } - if p.tok == .comma { - p.next() - p.fremove_last() - } - p.fgen_nl() - val++ - } - is_flag := p.attr == 'flag' - if is_flag && fields.len > 32 { - p.error('when an enum is used as bit field, it must have a max of 32 fields') - } - mut T := Type{ - name: enum_name - mod: p.mod - parent: 'int' - cat: .enum_ - enum_vals: fields.clone() - is_public: is_pub - is_flag: is_flag - } - p.table.tuple_variants[enum_name] = tuple_variants - if is_flag && !p.first_pass() { - p.gen_enum_flag_methods(mut T) - } - if p.pass == .decl || is_flag { - p.table.register_type(T) - } - // Register `Expression` enum - if tuple_variants.len > 0 && p.pass == .main { - p.cgen.typedefs << 'typedef struct { -void* obj; -int typ; -} $enum_name; -' - } - // Skip nameless enums - else if !no_name && !p.first_pass() { - p.cgen.typedefs << 'typedef int $enum_name;' - } - p.check(.rcbr) - p.fgen_nl() - p.fgen_nl() - if !no_name && fields.len == 0 { - p.error('Empty enums are not allowed.') - } -} - -fn (p mut Parser) check_enum_member_access() { - if p.expected_type.starts_with('Option_') { - p.expected_type = p.expected_type[7..] - } - tt := p.find_type(p.expected_type) - if tt.cat == .enum_ { - p.check(.dot) - val := p.check_name() - // Make sure this enum value exists - if !tt.has_enum_val(val) { - p.error('enum `$tt.name` does not have value `$val`') - } - p.gen(mod_gen_name(tt.mod) + '__' + p.expected_type + '_' + val) - } - else { - p.error('`$tt.name` is not an enum') - } -} - -/* - -enum Expression { -Boolean(bool), -Integer(i32), -} - -fn main() { - let expr = Expression::Integer(10); - let mut val = Expression::Boolean(true); - val = expr; - match val { - Expression::Integer(n) => println!("INT {}", n), - Expression::Boolean(b) => println!("BOOL {}", b), - } - - //println!("HELLO {}", val); -} -*/ - diff --git a/vlib/compiler/expression.v b/vlib/compiler/expression.v deleted file mode 100644 index 50e1a4ce8c..0000000000 --- a/vlib/compiler/expression.v +++ /dev/null @@ -1,958 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -fn (p mut Parser) bool_expression() string { - //is_ret := p.prev_tok == .key_return - start_ph := p.cgen.add_placeholder() - mut expected := p.expected_type - tok := p.tok - typ := p.bterm() - mut got_and := false // to catch `a && b || c` in one expression without () - mut got_or := false - for p.tok == .and || p.tok == .logical_or { - if p.tok == .and { - got_and = true - if got_or { - p.error(and_or_error) - } - } - if p.tok == .logical_or { - got_or = true - if got_and { - p.error(and_or_error) - } - } - if p.is_sql { - if p.tok == .and { - p.gen(' and ') - } - else if p.tok == .logical_or { - p.gen(' or ') - } - } - else { - p.gen(' ${p.tok.str()} ') - } - p.check_space(p.tok) - p.check_types(p.bterm(), typ) - if typ != 'bool' { - p.error('logical operators `&&` and `||` require booleans') - } - } - if typ == '' { - println('curline:') - println(p.cgen.cur_line) - println(tok.str()) - p.error('expr() returns empty type') - } - if p.inside_return_expr && p.expected_type.contains('_MulRet_') { //is_ret { // return a,b hack TODO - expected = p.expected_type - } - // `window.widget = button`, widget is an interface - if expected != typ && expected.ends_with('er') && expected.contains('I') { - tt := typ.replace('*', '_ptr') - /* - if p.fileis('button') || p.fileis('textbox') { - p.warn('exp="$expected" typ="$typ" tt="$tt"') - } - */ - p.cgen.set_placeholder(start_ph, - '($expected) { ._interface_idx = /* :) */ _${expected}_${tt}_index, ._object = ' ) - p.gen('}') - //p.satisfies_interface(expected, typ, true) - } - // e.g. `return InfixExpr{}` in a function expecting `Expr` - if expected != typ && expected in p.table.sum_types { // TODO perf - //p.warn('SUM CAST exp=$expected typ=$typ p.exp=$p.expected_type') - if typ in p.table.sum_types[expected] { - p.cgen.set_placeholder(start_ph, '/*SUM TYPE CAST2*/ ($expected) { .obj = memdup( &($typ[]) { ') - tt := typ.all_after('_') // TODO - p.gen('}, sizeof($typ) ), .typ = SumType_${expected}_${tt} }')//${val}_type }') - } - } - // `as` cast - // TODO remove copypasta - if p.tok == .key_as { - return p.key_as(typ, start_ph) - } - return typ -} - -fn (p mut Parser) key_as(typ string, start_ph int) string { - p.fspace() - p.next() - p.fspace() - cast_typ := p.get_type() - if typ == cast_typ { - p.error('casting `$typ` to `$cast_typ` is not needed') - } - if typ in p.table.sum_types { - if !(cast_typ in p.table.sum_types[typ]) { - p.error('cannot cast `$typ` to `$cast_typ`. `$cast_typ` is not a variant of `$typ`') - } - p.cgen.set_placeholder(start_ph, '*($cast_typ*)') - p.gen('.obj') - // Make sure the sum type can be cast, otherwise throw a runtime error - /* - sum_type:= p.cgen.cur_line.all_after('*) (').replace('.obj', '.typ') - - n := cast_typ.all_after('__') - p.cgen.insert_before('if (($sum_type != SumType_${typ}_$n) { -puts("runtime error: $p.file_name:$p.scanner.line_nr cannot cast sum type `$typ` to `$n`"); -exit(1); -} -') -*/ - } else { - p.error('`as` casts have been removed, use the old syntax: `Type(val)`') - } - return cast_typ -} - -fn (p mut Parser) bterm() string { - ph := p.cgen.add_placeholder() - mut typ := p.expression() - p.expected_type = typ - is_str := typ == 'string' && !p.is_sql - is_ustr := typ == 'ustring' - base := p.base_type(typ) - is_float := base[0] == `f` && (base in ['f64', 'f32']) && !(p.cur_fn.name in ['f64_abs', 'f32_abs']) && p.cur_fn.name != 'eq' - is_array := typ.starts_with('array_') - expr_type := base - tok := p.tok - /* - if tok == .assign { - p.error('no = ') - } - */ - - if tok in [.eq, .gt, .lt, .le, .ge, .ne] { - // TODO: remove when array comparing is supported - if is_array { - p.error('array comparison is not supported yet') - } - p.fspace() - // p.fgen(' ${p.tok.str()} ') - if (is_float || is_str || is_ustr) && !p.is_js { - p.gen(',') - } - else if p.is_sql && tok == .eq { - p.gen('=') - } - else { - p.gen(tok.str()) - } - p.next() - p.fspace() - // `id == user.id` => `id == $1`, `user.id` - if p.is_sql { - p.sql_i++ - p.gen('$' + p.sql_i.str()) - p.cgen.start_cut() - p.check_types(p.expression(), typ) - sql_param := p.cgen.cut() - p.sql_params << sql_param - p.sql_types << typ - // println('*** sql type: $typ | param: $sql_param') - } - else { - p.check_types(p.expression(), typ) - } - typ = 'bool' - if is_str && !p.is_js { - // && !p.is_sql { - p.gen(')') - match tok { - .eq { - p.cgen.set_placeholder(ph, 'string_eq(') - } - .ne { - p.cgen.set_placeholder(ph, 'string_ne(') - } - .le { - p.cgen.set_placeholder(ph, 'string_le(') - } - .ge { - p.cgen.set_placeholder(ph, 'string_ge(') - } - .gt { - p.cgen.set_placeholder(ph, 'string_gt(') - } - .lt { - p.cgen.set_placeholder(ph, 'string_lt(') - } - else { - }} - } - if is_ustr { - p.gen(')') - match tok { - .eq { - p.cgen.set_placeholder(ph, 'ustring_eq(') - } - .ne { - p.cgen.set_placeholder(ph, 'ustring_ne(') - } - .le { - p.cgen.set_placeholder(ph, 'ustring_le(') - } - .ge { - p.cgen.set_placeholder(ph, 'ustring_ge(') - } - .gt { - p.cgen.set_placeholder(ph, 'ustring_gt(') - } - .lt { - p.cgen.set_placeholder(ph, 'ustring_lt(') - } - else { - }} - } - if is_float && p.cur_fn.name != 'f32_abs' && p.cur_fn.name != 'f64_abs' { - p.gen(')') - match tok { - // NB: For more precision/stability, the == and != float - // comparisons are done with V functions that use the epsilon - // constants for the given type. - // Everything else uses native comparisons (C macros) for speed. - .eq { - p.cgen.set_placeholder(ph, '${expr_type}_eq(') - } - .ne { - p.cgen.set_placeholder(ph, '${expr_type}_ne(') - } - .le { - p.cgen.set_placeholder(ph, 'macro_${expr_type}_le(') - } - .ge { - p.cgen.set_placeholder(ph, 'macro_${expr_type}_ge(') - } - .gt { - p.cgen.set_placeholder(ph, 'macro_${expr_type}_gt(') - } - .lt { - p.cgen.set_placeholder(ph, 'macro_${expr_type}_lt(') - } - else { - }} - } - } - return typ -} - -// also called on *, &, @, . (enum) -fn (p mut Parser) name_expr() string { - p.has_immutable_field = false - p.is_const_literal = false - ph := p.cgen.add_placeholder() - // amp - ptr := p.tok == .amp - deref := p.tok == .mul - mut mul_nr := 0 - mut deref_nr := 0 - for { - if p.tok == .amp { - mul_nr++ - } - else if p.tok == .mul { - deref_nr++ - } - else { - break - } - p.next() - } - if p.tok == .lpar { - p.gen('*'.repeat(deref_nr)) - p.gen('(') - p.check(.lpar) - mut temp_type := p.bool_expression() - p.gen(')') - p.check(.rpar) - for _ in 0 .. deref_nr { - temp_type = temp_type.replace_once('*', '') - } - return temp_type - } - mut name := p.lit - // blank identifier (not var) - if name == '_' { - p.error('cannot use `_` as value') - } - // generic type check - if name in p.generic_dispatch.inst.keys() { - name = p.generic_dispatch.inst[name] - } - // Raw string (`s := r'hello \n ') - if name == 'r' && p.peek() == .string&& p.prev_tok != .str_dollar { - p.string_expr() - return 'string' - } - // C string (a zero terminated one) C.func( c'hello' ) - if name == 'c' && p.peek() == .string&& p.prev_tok != .str_dollar { - p.string_expr() - return 'charptr' - } - // known_type := p.table.known_type(name) - orig_name := name - is_c := name == 'C' && p.peek() == .dot - if is_c { - p.check(.name) - p.check(.dot) - name = p.lit - // C struct initialization - if p.peek() == .lcbr && p.expected_type == '' { - // not an expression - if !p.table.known_type(name) { - p.error('unknown C type `$name`, ' + 'define it with `struct C.$name { ... }`') - } - return p.get_struct_type(name, true, ptr) - } - if ptr && p.peek() == .lpar { - peek2 := p.tokens[p.token_idx + 1] - // `&C.Foo(0)` cast (replacing old `&C.Foo{!}`) - if peek2.tok == .number && peek2.lit == '0' { - p.cgen.insert_before('struct /*C.Foo(0)*/ ') - p.gen('0') - p.next() - p.next() - p.next() - p.next() - return name + '*' - } - // `&C.Foo(foo)` cast - p.cast(name + '*') - return name + '*' - } - // C function - if p.peek() == .lpar { - return p.get_c_func_type(name) - } - // C const (`C.GLFW_KEY_LEFT`) - p.gen(name) - p.next() - return 'int' - } - // enum value? (`color == .green`) - if p.tok == .dot { - if p.table.known_type(p.expected_type) { - p.check_enum_member_access() - // println("found enum value: $p.expected_type") - return p.expected_type - } - else { - p.error('unknown enum: `$p.expected_type`') - } - } - // Variable, checked before modules, so that module shadowing is allowed: - // `gg = gg.newcontext(); gg.draw_rect(...)` - if p.known_var_check_new_var(name) { - return p.get_var_type(name, ptr, deref_nr) - } - // Module? - if p.peek() == .dot && (name == p.mod || p.import_table.known_alias(name)) && !is_c { - mut mod := name - // must be aliased module - if name != p.mod && p.import_table.known_alias(name) { - p.import_table.register_used_import(name) - mod = p.import_table.resolve_alias(name) - } - p.next() - p.check(.dot) - name = p.lit - name = prepend_mod(mod_gen_name(mod), name) - } - // Unknown name, try prepending the module name to it - // TODO perf - else if !p.table.known_type(name) && !p.known_fn_in_mod(name) && !p.table.known_const(name) && !is_c { - name = p.prepend_mod(name) - } - // re-check - if p.known_var_check_new_var(name) { - return p.get_var_type(name, ptr, deref_nr) - } - // 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) { - // cast expression: float(5), byte(0), (*int)(ptr) etc - // if !is_c && ( p.peek() == .lpar || (deref && p.peek() == .rpar) ) { - if p.peek() == .lpar || (deref && p.peek() == .rpar) { - if deref { - name += '*'.repeat(deref_nr) - } - else if ptr { - name += '*'.repeat(mul_nr) - } - // p.gen('(') - mut typ := name - p.cast(typ) - // p.gen(')') - for p.tok == .dot { - typ = p.dot(typ, ph) - } - return typ - } - // Color.green - else if p.peek() == .dot { - is_arr_start := p.prev_tok == .lsbr - enum_type := p.table.find_type(name) - if enum_type.cat != .enum_ { - p.error('`$name` is not an enum') - } - p.next() - p.check(.dot) - val := p.lit - if !enum_type.has_enum_val(val) { - p.error('enum `$enum_type.name` does not have value `$val`') - } - if p.expected_type == enum_type.name && !is_arr_start { - // `if color == .red` is enough - // no need in `if color == Color.red` - p.warn('`${enum_type.name}.$val` is unnecessary, use `.$val`') - } - // `expr := Expr.BoolExpr(true)` => - // `Expr expr = { .obj = true, .typ = BoolExpr_type };` - if val[0].is_capital() { - p.next() - p.check(.lpar) - //println('sum type $val name=$val') - // Find a corresponding tuple variant - // TODO slow, but this will be re-written anyway - mut idx := 0 - for i, val_ in enum_type.enum_vals { - //println('f $field.name') - if val_ == val { - idx = i - } - } - q := p.table.tuple_variants[enum_type.name] - //println(q) - //println(q[idx]) - arg_type := q[idx] - p.gen('($enum_type.name) { .obj = ($arg_type[]) { ') - p.bool_expression() - p.check(.rpar) - p.gen('}, .typ = ${val}_type }') - return enum_type.name - } - // println('enum val $val') - p.gen(mod_gen_name(enum_type.mod) + '__' + enum_type.name + '_' + val) // `color = main__Color_green` - p.next() - return enum_type.name - } - // normal struct init (non-C) - else if p.peek() == .lcbr || p.peek() == .lt { - return p.get_struct_type(name, false, ptr) - } - } - // Constant - if p.table.known_const(name) { - return p.get_const_type(name, ptr) - } - // TODO: V script? Try os module. - // Function (not method, methods are handled in `.dot()`) - mut f := p.table.find_fn_is_script(name, p.v_script) or { - // First pass, the function can be defined later. - if p.first_pass() { - p.next() - return 'unresolved' - } - // exhaused all options type,enum,const,mod,var,fn etc - // so show undefined error (also checks typos) - p.undefined_error(name, orig_name) - return '' // panics - } - // no () after func, so func is an argument, just gen its name - // TODO verify this and handle errors - peek := p.peek() - if peek != .lpar && peek != .lt { - // Register anon fn type - fn_typ := Type{ - name: f.typ_str() // 'fn (int, int) string' - - mod: p.mod - func: f - } - p.table.register_type(fn_typ) - p.gen(p.table.fn_gen_name(f)) - p.next() - return f.typ_str() // 'void*' - } - // TODO bring back - if f.typ == 'void' && !p.inside_if_expr { - // p.error('`$f.name` used as value') - } - fn_call_ph := p.cgen.add_placeholder() - // println('call to fn $f.name of type $f.typ') - // TODO replace the following dirty hacks (needs ptr access to fn table) - new_f := f - p.fn_call(mut new_f, 0, '', '') - if f.is_generic { - _ = p.table.find_fn(f.name) or { - return '' - } - // println('after call of generic instance $new_f.name(${new_f.str_args(p.table)}) $new_f.typ') - // println(' from $f2.name(${f2.str_args(p.table)}) $f2.typ : $f2.type_inst') - } - f = new_f - // optional function call `function() or {}`, no return assignment - is_or_else := p.tok == .key_orelse - if p.tok == .question { - // `files := os.ls('.')?` - return p.gen_handle_question_suffix(f, fn_call_ph) - } - else if !p.is_var_decl && is_or_else { - f.typ = p.gen_handle_option_or_else(f.typ, '', fn_call_ph) - } - else if !p.is_var_decl && !is_or_else && !p.inside_return_expr && f.typ.starts_with('Option_') { - opt_type := f.typ[7..].replace('ptr_', '&') - p.error('unhandled option type: `?$opt_type`') - } - // dot after a function call: `get_user().age` - if p.tok == .dot { - mut typ := '' - for p.tok == .dot { - // println('dot #$dc') - typ = p.dot(f.typ, ph) - } - return typ - } - // p.log('end of name_expr') - if f.typ.ends_with('*') { - p.is_alloc = true - } - return f.typ -} - -// returns resulting type -fn (p mut Parser) expression() string { - p.is_const_literal = true - // if p.scanner.file_path.contains('test_test') { - // println('expression() pass=$p.pass tok=') - // p.print_tok() - // } - ph := p.cgen.add_placeholder() - typ := p.indot_expr() - is_str := typ == 'string' - is_ustr := typ == 'ustring' - // `a << b` ==> `array_push(&a, b)` - if p.tok == .left_shift { - if typ.contains('array_') { - // Can't pass integer literal, because push requires a void* - // a << 7 => int tmp = 7; array_push(&a, &tmp); - // _PUSH(&a, expression(), tmp, string) - tmp := p.get_tmp() - tmp_typ := parse_pointer(typ[6..]) // skip "array_" - //p.warn('arr typ $tmp_typ') - p.expected_type = tmp_typ - //println('set expr to $tmp_typ') - p.check_space(.left_shift) - // Get the value we are pushing - p.gen(', (') - // Immutable? Can we push? - if !p.expr_var.is_mut && !p.pref.translated { - p.error("`$p.expr_var.name` is immutable (can\'t <<)") - } - if p.expr_var.is_arg && p.expr_var.typ.starts_with('array_') { - p.error("for now it's not possible to append an element to " + 'a mutable array argument `$p.expr_var.name`') - } - if !p.expr_var.is_changed { - p.mark_var_changed(p.expr_var) - } - p.gen('/*typ = $typ tmp_typ=$tmp_typ*/') - ph_clone := p.cgen.add_placeholder() - expr_type := p.bool_expression() - // Need to clone the string when appending it to an array? - if p.pref.autofree && typ == 'array_string' && expr_type == 'string' { - p.cgen.set_placeholder(ph_clone, 'string_clone(') - p.gen(')') - } - p.gen_array_push(ph, typ, expr_type, tmp, tmp_typ) - return 'void' - } - else { - if !is_integer_type(typ) { - t := p.table.find_type(typ) - if t.cat != .enum_ { - p.error('cannot use shift operator on non-integer type `$typ`') - } - } - p.next() - p.gen(' << ') - p.check_types(p.expression(), 'integer') - return typ - } - } - if p.tok == .righ_shift { - if !is_integer_type(typ) { - t := p.table.find_type(typ) - if t.cat != .enum_ { - p.error('cannot use shift operator on non-integer type `$typ`') - } - } - p.next() - p.gen(' >> ') - p.check_types(p.expression(), 'integer') - return typ - } - // + - | ^ - for p.tok in [.plus, .minus, .pipe, .amp, .xor] { - tok_op := p.tok - if typ == 'bool' { - p.error('operator ${p.tok.str()} not defined on bool ') - } - is_num := typ.contains('*') || is_number_type(typ) || is_number_type(p.base_type(typ)) - p.check_space(p.tok) - if is_str && tok_op == .plus && !p.is_js { - p.is_alloc = true - p.cgen.set_placeholder(ph, 'string_add(') - p.gen(',') - } - else if is_ustr && tok_op == .plus { - p.cgen.set_placeholder(ph, 'ustring_add(') - p.gen(',') - } - // 3 + 4 - else if is_num || p.is_js { - if typ == 'void*' { - // Msvc errors on void* pointer arithmatic - // ... So cast to byte* and then do the add - p.cgen.set_placeholder(ph, '(byte*)') - } - else if typ.contains('*') { - p.cgen.set_placeholder(ph, '($typ)') - } - p.gen(tok_op.str()) - } - // Vec + Vec - else { - if p.pref.translated { - p.gen(tok_op.str() + ' /*doom hack*/') // TODO hack to fix DOOM's angle_t - } - else { - p.gen(',') - } - } - if is_str && tok_op != .plus { - p.error('strings only support `+` operator') - } - expr_type := p.term() - open := tok_op == .amp && p.tok in [.eq, .ne] // force precedence `(a & b) == c` //false - if tok_op in [.pipe, .amp, .xor] { - if !(is_integer_type(expr_type) && is_integer_type(typ)) { - p.error('operator ${tok_op.str()} is defined only on integer types') - } - // open = true - } - if open { - p.cgen.set_placeholder(ph, '(') - } - p.check_types(expr_type, typ) - if (is_str || is_ustr) && tok_op == .plus && !p.is_js { - p.gen(')') - } - if open { - p.gen(')') - } - // Make sure operators are used with correct types - if !p.pref.translated && !is_str && !is_ustr && !is_num { - T := p.table.find_type(typ) - if tok_op == .plus { - p.handle_operator('+', typ, 'op_plus', ph, T) - } - else if tok_op == .minus { - p.handle_operator('-', typ, 'op_minus', ph, T) - } - } - } - // `as` cast - // TODO remove copypasta - if p.tok == .key_as { - return p.key_as(typ, ph) - } - return typ -} - -fn (p mut Parser) handle_operator(op string, typ string,cpostfix string, ph int, tt &Type) { - if tt.has_method(op) { - p.cgen.set_placeholder(ph, '${typ}_${cpostfix}(') - p.gen(')') - } - else if typ != 'unresolved' { - p.error('operator $op not defined on `$typ`') - } -} - -fn (p mut Parser) term() string { - line_nr := p.scanner.line_nr - // if p.fileis('fn_test') { - // println('\nterm() $line_nr') - // } - ph := p.cgen.add_placeholder() - typ := p.unary() - // if p.fileis('fn_test') { - // println('2: $line_nr') - // } - // `*` on a newline? Can't be multiplication, only dereference - if p.tok == .mul && line_nr != p.scanner.line_nr { - return typ - } - for p.tok in [.mul, .div, .mod] { - tok := p.tok - is_mul := tok == .mul - is_div := tok == .div - is_mod := tok == .mod - p.fspace() - p.next() - p.gen(tok.str()) // + ' /*op2*/ ') - oph := p.cgen.add_placeholder() - p.fspace() - if (is_div || is_mod) && p.tok == .number && p.lit == '0' { - p.error('division or modulo by zero') - } - expr_type := p.unary() - if (is_mul || is_div) && expr_type == 'string' { - p.error('operator ${tok.str()} cannot be used on strings') - } - if !is_primitive_type(expr_type) && expr_type == typ { - p.check_types(expr_type, typ) - T := p.table.find_type(typ) - // NB: oph is a char index just after the OP - before_oph := p.cgen.cur_line[..oph - 1] - after_oph := p.cgen.cur_line[oph..] - p.cgen.cur_line = before_oph + ',' + after_oph - match tok { - .mul { - p.handle_operator('*', typ, 'op_mul', ph, T) - } - .div { - p.handle_operator('/', typ, 'op_div', ph, T) - } - .mod { - p.handle_operator('%', typ, 'op_mod', ph, T) - } - else { - }} - continue - } - if is_mod { - if !(is_integer_type(expr_type) && is_integer_type(typ)) { - p.error('operator `mod` requires integer types') - } - } - else { - p.check_types(expr_type, typ) - } - } - return typ -} - -fn (p mut Parser) unary() string { - mut typ := '' - tok := p.tok - match tok { - .not { - p.gen('!') - p.check(.not) - // typ should be bool type - typ = p.indot_expr() - if typ != 'bool' { - p.error('operator ! requires bool type, not `$typ`') - } - } - .bit_not { - p.gen('~') - p.check(.bit_not) - typ = p.bool_expression() - } - else { - typ = p.factor() - }} - return typ -} - -fn (p mut Parser) factor() string { - mut typ := '' - tok := p.tok - match tok { - .key_none { - if !p.expected_type.starts_with('Option_') { - p.error('need "$p.expected_type" got none') - } - p.gen('opt_none()') - p.check(.key_none) - return p.expected_type - } - .number { - // Check if float (`1.0`, `1e+3`) but not if is hexa (e.g. 0xEE contains `E` but is not float) - typ = if (p.lit.contains('.') || p.lit.contains('e') || p.lit.contains('E')) && !(p.lit[..2] in ['0x', '0X']) { 'f64' } else { 'int' } - if p.expected_type != '' && !is_valid_int_const(p.lit, p.expected_type) { - p.error('constant `$p.lit` overflows `$p.expected_type`') - } - p.gen(p.lit) - } - .minus { - p.gen('-') - p.next() - return p.factor() - // Variable - } - .key_sizeof { - p.gen('sizeof(') - // p.fgen('sizeof(') - p.next() - p.check(.lpar) - mut sizeof_typ := p.get_type() - p.check(.rpar) - p.gen('$sizeof_typ)') - // p.fgen('$sizeof_typ)') - return 'int' - } - .key_typeof { - p.next() - p.check(.lpar) - p.cgen.nogen = true - vname := if p.tok == .name && p.peek() == .rpar { p.lit } else { '' } - type_of_var := p.expression() - p.cgen.nogen = false - p.check(.rpar) - is_sum_type := type_of_var in p.table.sum_types - if is_sum_type && vname.len > 0 { - // TODO: make this work for arbitrary sumtype expressions, not just simple vars - // NB: __SumTypeNames__[xxx][0] is the name of the sumtype itself; - // idx>0 are the names of the sumtype children - p.gen('tos3(__SumTypeNames__${type_of_var}[${vname}.typ])') - }else{ - p.gen('tos3("$type_of_var")') - } - return 'string' - } - .key_nameof { - p.next() - p.check(.lpar) - mut nameof_typ := p.get_type() - p.check(.rpar) - p.gen('tos3("$nameof_typ")') - return 'string' - } - .key_offsetof { - p.next() - p.check(.lpar) - offsetof_typ := p.get_type() - p.check(.comma) - member := p.check_name() - p.check(.rpar) - p.gen('__offsetof($offsetof_typ, $member)') - return 'int' - } - .amp, .dot, .mul { - // (dot is for enum vals: `.green`) - return p.name_expr() - } - .name { - // map[string]int - if p.lit == 'map' && p.peek() == .lsbr { - return p.map_init() - } - if p.lit == 'json' && p.peek() == .dot { - if !('json' in p.table.imports) { - p.error('undefined: `json`, use `import json`') - } - p.import_table.register_used_import('json') - return p.js_decode() - } - // if p.fileis('orm_test') { - // println('ORM name: $p.lit') - // } - typ = p.name_expr() - return typ - } - /* - .key_default { - p.next() - p.next() - name := p.check_name() - if name != 'T' { - p.error('default needs T') - } - p.gen('default(T)') - p.next() - return 'T' - } - */ - - .lpar { - // p.gen('(/*lpar*/') - p.gen('(') - p.check(.lpar) - typ = p.bool_expression() - // Hack. If this `)` referes to a ptr cast `(*int__)__`, it was already checked - // TODO: fix parser so that it doesn't think it's a par expression when it sees `(` in - // __(__*int)( - if !p.ptr_cast { - p.check(.rpar) - } - p.ptr_cast = false - p.gen(')') - return typ - } - .chartoken { - p.char_expr() - typ = 'byte' - return typ - } - .string{ - p.string_expr() - typ = 'string' - return typ - } - .key_false { - typ = 'bool' - p.gen('0') - } - .key_true { - typ = 'bool' - p.gen('1') - } - .lsbr { - // `[1,2,3]` or `[]` or `[20]byte` - // TODO have to return because arrayInit does next() - // everything should do next() - return p.array_init() - } - .lcbr { - // `m := { 'one': 1 }` - if p.peek() == .string{ - return p.map_init() - } - peek2 := p.tokens[p.token_idx + 1] - if p.peek() == .rcbr || (p.peek() == .name && peek2.tok == .colon) { - return p.struct_init(p.expected_type) - } - // { user | name :'new name' } - return p.assoc() - } - .key_if { - typ = p.if_statement(true, 0) - return typ - } - .key_match { - typ = p.match_statement(true) - return typ - } - else { - if p.pref.verbosity.is_higher_or_equal(.level_three) { - next := p.peek() - println('prev=${p.prev_tok.str()}') - println('next=${next.str()}') - } - p.error('unexpected token: `${p.tok.str()}`') - }} - p.next() // TODO everything should next() - return typ -} - -// { user | name: 'new name' } - diff --git a/vlib/compiler/fn.v b/vlib/compiler/fn.v deleted file mode 100644 index 24a5d9ed23..0000000000 --- a/vlib/compiler/fn.v +++ /dev/null @@ -1,1742 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import ( - strings -) - -const ( - MaxLocalVars = 50 -) - -pub struct Fn { - // addr int - // pub: -mut: - name string - mod string - // local_vars []Var - // var_idx int - args []Var - is_interface bool - // called_fns []string - // idx int - scope_level int - typ string // return type - receiver_typ string - is_c bool - is_public bool - is_method bool - is_decl bool // type myfn fn(int, int) - is_unsafe bool - is_deprecated bool - is_variadic bool - is_generic bool - returns_error bool - defer_text []string - type_pars []string - type_inst []TypeInst - generic_fn_idx int - parser_idx int - fn_name_token_idx int // used by error reporting - comptime_define string - is_used bool // so that we can skip unused fns in resulting C code - // x64_addr i64 // address in the generated x64 binary -} - -struct TypeInst { -mut: -// an instantiation of generic params (e.g. ["int","int","double"]) - inst map[string]string - done bool -} - -const ( - EmptyFn = Fn{ - } - MainFn = Fn{ - name: 'main' - } -) - -pub fn (a []TypeInst) str() string { - mut r := []string - for t in a { - mut s := ' | ' - for k in t.inst.keys() { - s += k + ' -> ' + t.inst[k] + ' | ' - } - r << s - } - return r.str() -} - -fn (p &Parser) find_var_or_const(name string) ?Var { - if p.known_var(name) { - return p.find_var(name) - } - if p.table.known_const(name) { - return p.table.find_const(name) - } - modname := p.prepend_mod(name) - if p.table.known_const(modname) { - return p.table.find_const(modname) - } - return none -} - -fn (p &Parser) find_var(name string) ?Var { - for i in 0 .. p.var_idx { - if p.local_vars[i].name == name { - return p.local_vars[i] - } - } - return none -} - -fn (p &Parser) find_var_check_new_var(name string) ?Var { - for i in 0 .. p.var_idx { - if p.local_vars[i].name == name { - return p.local_vars[i] - } - } - // A hack to allow `newvar := Foo{ field: newvar }` - // Declare the variable so that it can be used in the initialization - if name == 'main__' + p.var_decl_name { - return Var{ - name: p.var_decl_name - typ: 'voidptr' - is_mut: true - } - } - return none -} - -fn (p mut Parser) open_scope() { - p.cur_fn.defer_text << '' - p.cur_fn.scope_level++ -} - -fn (p mut Parser) mark_var_used(v Var) { - if v.idx == -1 || v.idx >= p.local_vars.len { - return - } - p.local_vars[v.idx].is_used = true -} - -fn (p mut Parser) mark_var_returned(v Var) { - if v.idx == -1 || v.idx >= p.local_vars.len { - return - } - p.local_vars[v.idx].is_returned = true -} - -fn (p mut Parser) mark_var_changed(v Var) { - if v.idx == -1 || v.idx >= p.local_vars.len { - return - } - p.local_vars[v.idx].is_changed = true -} - -fn (p mut Parser) mark_arg_moved(v Var) { - for i, arg in p.cur_fn.args { - if arg.name == v.name { - // println('setting f $p.cur_fn.name arg $arg.name to is_mut') - p.cur_fn.args[i].is_moved = true - break - } - } - p.table.fns[p.cur_fn.name] = p.cur_fn -} - -fn (p &Parser) known_var(name string) bool { - _ = p.find_var(name) or { - return false - } - return true -} - -fn (p &Parser) known_var_check_new_var(name string) bool { - _ = p.find_var_check_new_var(name) or { - return false - } - return true -} - -fn (p mut Parser) register_var(v Var) { - mut new_var := { - v | - idx:p.var_idx, - scope_level:p.cur_fn.scope_level - } - if v.line_nr == 0 { - new_var.token_idx = p.cur_tok_index() - new_var.line_nr = p.cur_tok().line_nr - } - // Expand the array - if p.var_idx >= p.local_vars.len { - p.local_vars << new_var - } - else { - p.local_vars[p.var_idx] = new_var - } - p.var_idx++ -} - -fn (p mut Parser) clear_vars() { - // shared a := [1, 2, 3] - p.var_idx = 0 - if p.local_vars.len > 0 { - if p.pref.autofree { - // p.local_vars.free() - } - p.local_vars = [] - } -} - -// Function signatures are added to the top of the .c file in the first run. -fn (p mut Parser) fn_decl() { - p.clear_vars() // clear local vars every time a new fn is started - defer { - p.fgen_nl() - p.fgen_nl() - } - fn_start_idx := p.cur_tok_index() - // If we are in the first pass, create a new function. - // In the second pass fetch the one we created. - /* - mut f := if p.first_pass { - Fn{ - mod: p.mod - is_public: p.tok == .key_pub - } - else { - } - */ - - is_pub := p.tok == .key_pub - mut f := Fn{ - mod: p.mod - is_public: is_pub || p.is_vh // functions defined in .vh are always public - - is_unsafe: p.attr == 'unsafe_fn' - is_deprecated: p.attr == 'deprecated' - comptime_define: if p.attr.starts_with('if ') { p.attr[3..] } else { '' } - } - is_live := p.pref.is_live && p.attr == 'live' - is_solive := p.pref.is_solive && p.attr == 'live' - if is_pub { - p.next() - p.fspace() - } - p.returns = false - // p.gen('/* returns $p.returns */') - p.next() - p.fspace() - // Method receiver - mut receiver_typ := '' - if p.tok == .lpar { - f.is_method = true - p.check(.lpar) - receiver_name := p.check_name() - p.fspace() - is_mut := p.tok == .key_mut - is_amp := p.tok == .amp - if is_mut || is_amp { - p.check(p.tok) - if !is_amp { - p.fspace() - } - } - receiver_typ = p.get_type() - t := p.table.find_type(receiver_typ) - if (t.name == '' || t.is_placeholder) && !p.first_pass() { - p.error('unknown receiver type `$receiver_typ`') - } - if t.cat == .interface_ { - p.error('invalid receiver type `$receiver_typ` (`$receiver_typ` is an interface)') - } - // Don't allow modifying types from a different module - if !p.first_pass() && !p.builtin_mod && t.mod != p.mod && !p.is_vgen // let vgen define methods like .str() on types defined in other modules - { - // println('T.mod=$T.mod') - // println('p.mod=$p.mod') - p.error('cannot define new methods on non-local type `$receiver_typ`') - } - // `(f *Foo)` instead of `(f mut Foo)` is a common mistake - if receiver_typ.ends_with('*') { - tt := receiver_typ.replace('*', '') - p.error('use `($receiver_name mut $tt)` instead of `($receiver_name *$tt)`') - } - f.receiver_typ = receiver_typ - if is_mut || is_amp { - receiver_typ += '*' - } - p.check(.rpar) - p.fspace() - receiver := Var{ - name: receiver_name - is_arg: true - typ: receiver_typ - is_mut: is_mut - ref: is_amp - ptr: is_mut - line_nr: p.scanner.line_nr - token_idx: p.cur_tok_index() - } - f.args << receiver - p.register_var(receiver) - } - // +-/* methods (operator overloading) - mut is_op := false - if p.tok in [.plus, .minus, .mul, .div, .mod] { - f.name = p.tok.str() - p.next() - is_op = true - } - else { - f.name = p.check_name() - } - f.fn_name_token_idx = p.cur_tok_index() - // init fn - if f.name == 'init' && !f.is_method && f.is_public && !p.is_vh { - p.error('init function cannot be public') - } - // .str() methods - if f.is_method && f.name == 'str' && !f.is_public { - p.error('.str() methods must be declared as public') - } - // C function header def? (fn C.NSMakeRect(int,int,int,int)) - is_c := f.name == 'C' && p.tok == .dot - // Just fn signature? only builtin.v + default build mode - // if p.is_vh { - // if f.name == 'main' { - // println('\n\nfn_decl() name=$f.name pass=$p.pass $p.file_name receiver_typ=$receiver_typ nogen=$p.cgen.nogen') - // } - if is_c { - p.check(.dot) - f.name = p.check_name() - f.is_c = true - } - orig_name := f.name - // simple_name := f.name - // user.register() => User_register() - has_receiver := receiver_typ.len > 0 - if receiver_typ != '' { - // f.name = '${receiver_typ}_${f.name}' - } - // full mod function name - // `os.exit()` ==> `os__exit()` - // if !is_c && !p.builtin_mod && receiver_typ.len == 0 { - if !is_c && !has_receiver && (!p.builtin_mod || (p.builtin_mod && f.name == 'init')) { - f.name = p.prepend_mod(f.name) - } - if p.first_pass() && receiver_typ.len == 0 { - if existing_fn:=p.table.find_fn(f.name){ - // This existing function could be defined as C decl before - // (no body), then we don't need to throw an error. - if !existing_fn.is_decl { - p.error('redefinition of `$f.name`') - } - } - } - // Generic? - if p.tok == .lt { - // instance (dispatch) - if p.generic_dispatch.inst.size > 0 { - rename_generic_fn_instance(mut f, p.generic_dispatch) - } - else { - f.is_generic = true - } - p.next() - for { - type_par := p.check_name() - if type_par.len > 1 || !(type_par in reserved_type_param_names) { - p.error('type parameters must be single-character, upper-case letters of the following set: $reserved_type_param_names') - } - if type_par in f.type_pars { - p.error('redeclaration of type parameter `$type_par`') - } - f.type_pars << type_par - if p.tok == .gt { - break - } - p.check(.comma) - } - p.check(.gt) - p.set_current_fn(f) - } - // Args (...) - p.fn_args(mut f) - if is_op { - if f.args.len != 1 + 1 { - // +1 is for the receiver - p.error('operator overloading methods must have only 1 argument') - } - if f.args[0].typ != f.args[1].typ { - p.error('operators must have the same types on both sides') - } - } - // Returns an error? - if p.tok == .not { - p.next() - f.returns_error = true - } - // Returns a type? - mut typ := 'void' - if p.tok in [.name, .mul, .amp, .lsbr, .question, .lpar] { - p.fspace() - typ = p.get_type() - } - // V allows empty functions (just definitions) - is_fn_header := !is_c && !p.is_vh && p.tok != .lcbr - if is_fn_header { - f.name = orig_name // don't prepend module to external fn defs - f.is_decl = true - } - // Make sure the name is valid - if !is_c && !p.pref.translated && !is_fn_header { - if contains_capital(orig_name) && !p.fileis('view.v') && !p.is_vgen { - // println(orig_name) - p.error('function names cannot contain uppercase letters, use snake_case instead') - } - if f.name[0] == `_` { - p.error('function names cannot start with `_`, use snake_case instead') - } - if orig_name.contains('__') { - p.error('function names cannot contain double underscores, use single underscores instead') - } - } - // `{` required only in normal function declarations - if !is_c && !p.is_vh && !is_fn_header { - p.fspace() - p.check(.lcbr) - // p.fgen_nl() - } - // Register ?option type for return value and args - if typ.starts_with('Option_') { - p.cgen.typedefs << 'typedef Option $typ;' - // p.cgen.typedefs << 'typedef struct Option_$typ Option_$typ' - } - for arg in f.args { - if arg.typ.starts_with('Option_') { - p.cgen.typedefs << 'typedef Option $arg.typ;' - } - } - // Register function - f.typ = typ - str_args := f.str_args(p.table) - // Special case for main() args - if f.name == 'main__main' && !has_receiver { - if p.pref.backend == .x64 && !p.first_pass() { - //p.x64.save_main_fn_addr() - } - if str_args != '' || typ != 'void' { - p.error_with_token_index('fn main must have no arguments and no return values', f.fn_name_token_idx) - } - } - dll_export_linkage := p.get_linkage_prefix() - p.set_current_fn(f) - // Generate `User_register()` instead of `register()` - // Internally it's still stored as "register" in type User - mut fn_name_cgen := p.table.fn_gen_name(f) - // Start generation of the function body - skip_main_in_test := false - if !is_c && !is_live && !p.is_vh && !is_fn_header && !skip_main_in_test { - if p.pref.obfuscate { - p.genln('; // $f.name') - } - // Generic functions are inserted as needed from the call site - if f.is_generic && !p.scanner.is_fmt { - if p.first_pass() { - if !p.scanner.is_vh { - gpidx := p.v.get_file_parser_index(p.file_path) or { - panic('error finding parser for: $p.file_path') - } - f.parser_idx = gpidx - } - f.generic_fn_idx = fn_start_idx - if f.is_method { - rcv := p.table.find_type(receiver_typ) - if p.first_pass() && rcv.name == '' { - p.error('cannot currently add generic method to a type declared after it or in another module') - } - // println('added generic method r:$rcv.name f:$f.name') - p.add_method(rcv.name, f) - } - else { - p.table.register_fn(f) - } - } - p.set_current_fn(EmptyFn) - p.skip_fn_body() - return - } - else { - p.gen_fn_decl(f, typ, str_args) - } - } - if is_fn_header { - p.genln('$typ $fn_name_cgen ($str_args);') - p.fgen_nl() - } - if is_c { - p.fgen_nl() - } - - // Register the method - if receiver_typ != '' { - mut receiver_t := p.table.find_type(receiver_typ) - // No such type yet? It could be defined later. Create a new type. - // struct declaration later will modify it instead of creating a new one. - if p.first_pass() && receiver_t.name == '' { - // println('fn decl ! registering placeholder $receiver_typ') - receiver_t = Type{ - name: receiver_typ.replace('*', '') - mod: p.mod - is_placeholder: true - } - p.table.register_type(receiver_t) - } - p.add_method(receiver_t.name, f) - } - else if p.first_pass() { - // println('register_fn $f.name typ=$typ isg=$is_generic pass=$p.pass ' + - // '$p.file_name') - p.table.register_fn(f) - } - - if p.first_pass() && p.attr == 'live' && !(is_live || is_solive) { - println('INFO: run `v -live $p.v.pref.path `, if you want to use [live] function $f.name .') - } - - if p.is_vh || p.first_pass() || is_live || is_fn_header || skip_main_in_test { - // First pass? Skip the body for now - // Look for generic calls. - if !p.is_vh && !is_fn_header { - p.skip_fn_body() - } - // Live code reloading? Load all fns from .so - if is_live && p.first_pass() && p.mod == 'main' { - // println('ADDING SO FN $fn_name_cgen') - p.cgen.so_fns << fn_name_cgen - fn_name_cgen = '(* $fn_name_cgen )' - } - // Function definition that goes to the top of the C file. - mut fn_decl := '$dll_export_linkage$typ $fn_name_cgen ($str_args)' - if p.pref.obfuscate { - fn_decl += '; // $f.name' - } - // Add function definition to the top - if !is_c && p.first_pass() { - p.cgen.fns << fn_decl + ';' - } - return - } - - if is_solive { - // Live functions are protected by a mutex, because otherwise they - // can be changed by the live reload thread, *while* they are - // running, with unpredictable results (usually just crashing). - // For this purpose, the actual body of the live function, - // is put under a non publicly accessible function, that is prefixed - // with 'impl_live_' . - // The live function just calls its implementation dual, while ensuring - // that the call is wrapped by the mutex lock & unlock calls. - // Adding the mutex lock/unlock inside the body of the implementation - // function is not reliable, because the implementation function can do - // an early exit, which will leave the mutex locked. - function_args := f.str_args_without_types(p.table) - mut live_fncall := 'impl_live_${fn_name_cgen}(${function_args});' - mut live_fnreturn := '' - if typ != 'void' { - live_fncall = '$typ res = $live_fncall' - live_fnreturn = 'return res;' - } - p.genln(' pthread_mutex_lock(&live_fn_mutex);') - p.genln(' $live_fncall') - p.genln(' pthread_mutex_unlock(&live_fn_mutex);') - p.genln(' $live_fnreturn') - p.genln('}') - p.genln('$typ impl_live_${fn_name_cgen} ($str_args){') - } - - if f.name in ['main__main', 'main', 'WinMain'] { - if p.pref.is_test { - p.error_with_token_index('tests cannot have function `main`', f.fn_name_token_idx) - } - } - // println('is_c=$is_c name=$f.name') - if is_c || p.is_vh || is_fn_header { - return - } - // Profiling mode? Start counting at the beginning of the function (save current time). - if p.pref.is_prof && f.name != 'time__ticks' { - p.genln('double _PROF_START = time__ticks();//$f.name') - cgen_name := p.table.fn_gen_name(f) - if f.defer_text.len > f.scope_level { - f.defer_text[f.scope_level] = ' ${cgen_name}_time += time__ticks() - _PROF_START;' - } - } - if p.pref.backend == .x64 { - //p.x64.register_function_address(f.name) - } - p.statements_no_rcbr() - // p.cgen.nogen = false - // Print counting result after all statements in main - if p.pref.is_prof && f.name == 'main' { - p.genln(p.print_prof_counters()) - } - // Counting or not, always need to add defer before the end - if f.defer_text.len > f.scope_level { - p.genln(f.defer_text[f.scope_level]) - } - if typ != 'void' && !p.returns { - p.error_with_token_index('$f.name must return "$typ"', f.fn_name_token_idx) - } - if p.pref.backend == .x64 && f.name == 'main__main' && !p.first_pass() { - //p.x64.gen_exit() - } - if p.pref.backend == .x64 && !p.first_pass() { - //p.x64.ret() - } - // {} closed correctly? scope_level should be 0 - if p.mod == 'main' { - // println(p.cur_fn.scope_level) - } - if p.cur_fn.scope_level > 2 { - // p.error('unclosed {') - } - // Make sure all vars in this function are used (only in main for now) - /* - if p.mod != 'main' { - p.genln('}') - return - } - */ - - p.genln('}') - if !p.builtin_mod && p.mod != 'os' { - p.check_unused_and_mut_vars() - } - p.set_current_fn(EmptyFn) - p.returns = false -} - -[inline] -// Skips the entire function's body in the first pass. -fn (p mut Parser) skip_fn_body() { - mut opened_scopes := 0 - mut closed_scopes := 0 - for { - if p.tok == .lcbr { - opened_scopes++ - } - if p.tok == .rcbr { - closed_scopes++ - } - // find `foo()` in function bodies and register generic types - // TODO - // ... - // Reached a declaration token? (fn, struct, const etc) Stop. - if p.tok.is_decl() { - break - } - // fn body ended, and a new fn attribute declaration like [live] is starting? - if closed_scopes > opened_scopes && p.prev_tok == .rcbr { - if p.tok == .lsbr { - break - } - } - p.next() - } -} - -fn (p &Parser) get_linkage_prefix() string { - return if p.pref.ccompiler == 'msvc' && p.attr == 'live' && p.pref.is_so { '__declspec(dllexport) ' } else if p.attr == 'inline' { 'static inline ' } else { '' } -} - -fn (p mut Parser) check_unused_and_mut_vars() { - for var in p.local_vars { - if var.name == '' { - break - } - if !var.is_used && !p.pref.is_repl && !var.is_arg && !p.pref.translated && - var.name != 'tmpl_res' && p.mod != 'vweb' && var.name != 'it' && !p.cur_fn.is_unsafe { - p.production_error_with_token_index('`$var.name` declared and not used', var.token_idx) - } - if !var.is_changed && var.is_mut && !p.pref.is_repl && !p.pref.translated && var.name != 'it' && var.typ != 'T*' && p.mod != 'ui' && var.typ != 'App*' { - p.warn_or_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) { - p.verify_fn_before_call(f) - // println('\nfn_call $f.name is_method=$f.is_method receiver_type=$f.receiver_type') - // p.print_tok() - mut thread_name := '' - // Normal function => just its name, method => TYPE_FN.name - mut fn_name := f.name - if f.is_method { - fn_name = receiver_type.replace('*', '') + '_' + f.name - // fn_name = '${receiver_type}_${f.name}' - } - // Generate tmp struct with args - arg_struct_name := 'thread_arg_$fn_name' - tmp_struct := p.get_tmp() - p.genln('$arg_struct_name * $tmp_struct = malloc(sizeof($arg_struct_name));') - mut arg_struct := 'typedef struct $arg_struct_name { ' - p.next() - p.check(.lpar) - // str_args contains the args for the wrapper function: - // wrapper(arg_struct * arg) { fn("arg->a, arg->b"); } - mut str_args := '' - mut did_gen_something := false - for i, arg in f.args { - arg_struct += '$arg.typ $arg.name ;' // Add another field (arg) to the tmp struct definition - str_args += 'arg $dot_ptr $arg.name' - if i == 0 && f.is_method { - p.genln('$tmp_struct $dot_ptr $arg.name = $receiver_var ;') - if i < f.args.len - 1 { - str_args += ',' - } - did_gen_something = true - continue - } - // Set the struct values (args) - p.genln('$tmp_struct $dot_ptr $arg.name = ') - p.expression() - p.genln(';') - if i < f.args.len - 1 { - p.check(.comma) - str_args += ',' - } - did_gen_something = true - } - if !did_gen_something { - // Msvc doesnt like empty struct - arg_struct += 'EMPTY_STRUCT_DECLARATION;' - } - arg_struct += '} $arg_struct_name ;' - // Also register the wrapper, so we can use the original function without modifying it - fn_name = p.table.fn_gen_name(f) - wrapper_name := '${fn_name}_thread_wrapper' - mut wrapper_type := 'void*' - if p.os == .windows { - wrapper_type = 'DWORD WINAPI' - } - wrapper_text := '$wrapper_type $wrapper_name ($arg_struct_name * arg) {$fn_name ( /*f*/$str_args ); return 0; }' - p.cgen.register_thread_fn(wrapper_name, wrapper_text, arg_struct) - // Create thread object - tmp_nr := p.get_tmp_counter() - thread_name = '_thread$tmp_nr' - if p.os != .windows { - p.genln('pthread_t $thread_name;') - } - tmp2 := p.get_tmp() - mut parg := 'NULL' - if f.args.len > 0 { - parg = ' $tmp_struct' - } - // Call the wrapper - if p.os == .windows { - p.genln(' CreateThread(0,0, (LPTHREAD_START_ROUTINE)$wrapper_name, $parg, 0,0);') - } - else { - p.genln('int $tmp2 = pthread_create(& $thread_name, NULL, (void *)$wrapper_name, $parg);') - } - p.check(.rpar) -} - -fn (p mut Parser) verify_fn_before_call(f &Fn) { - if f.is_unsafe && !p.builtin_mod && !p.inside_unsafe { - p.warn('you are calling an unsafe function outside of an unsafe block') - } - if f.is_deprecated { - p.warn('$f.name is deprecated') - } - if !f.is_public && !f.is_c && !p.pref.is_test && !f.is_interface && f.mod != p.mod { - if f.name == 'contains' { - println('use `value in numbers` instead of `numbers.contains(value)`') - } - p.error('function `$f.name` is private') - } -} - -// p.tok == fn_name -fn (p mut Parser) fn_call(f mut Fn, method_ph int, receiver_var, receiver_type string) { - p.verify_fn_before_call(f) - is_comptime_define := f.comptime_define != '' && !(f.comptime_define in p.v.pref.compile_defines ) - if is_comptime_define { - p.cgen.nogen = true - } - if p.pref.backend == .x64 && !p.first_pass() { - //p.x64.call_fn(f.name) - } - p.calling_c = f.is_c - if f.is_c && !p.builtin_mod { - if f.name == 'free' { - p.error('use `free()` instead of `C.free()`') - } - else if f.name == 'malloc' { - p.error('use `malloc()` instead of `C.malloc()`') - } - } - f.is_used = true - cgen_name := p.table.fn_gen_name(f) - p.next() // fn name - mut generic_param_types := []string - if p.tok == .lt { - p.check(.lt) - for { - param_type := p.check_name() - generic_param_types << param_type - if p.tok != .comma { - break - } - p.check(.comma) - } - p.check(.gt) - // mut i := p.token_idx - // for { - // if p.tokens[i].tok == .gt { - // //p.error('explicit type arguments are not allowed; remove `<...>`') - // } else if p.tokens[i].tok == .lpar { - // // probably a typo, do not concern the user with the above error message - // break - // } - // i++ - // } - } - // if p.pref.is_prof { - // p.cur_fn.called_fns << cgen_name - // } - // If we have a method placeholder, - // we need to preappend "method(receiver, ...)" - if f.is_method { - receiver := f.args.first() - mut receiver_is_interface := false - if receiver.typ.ends_with('er') || receiver.typ[0] == `I` { - // I absolutely love this syntax - // `s.speak()` => - // `((void (*)())(Speaker_name_table[s._interface_idx][1]))(s._object); - // where `1` refers to the speak method, since it's the second method - // of the Speaker interface - t := p.table.find_type(receiver.typ) - if t.cat == .interface_ { - // Find the index of the method - mut idx := 0 - for i, method in t.methods { - if method.name == f.name { - idx = i - } - } - var := p.expr_var.name - iname := f.args[0].typ // Speaker - // remove useless var - if p.cgen.is_tmp { - p.cgen.tmp_line = p.cgen.tmp_line.replace(' $var', '') - } - else { - p.cgen.cur_line = p.cgen.cur_line.replace(' $var', '') - } - p.gen('(($f.typ (*)())(${iname}_name_table[${var}._interface_idx][$idx]))(${var}._object') - receiver_is_interface = true - } - } - // println('r=$receiver.typ RT=$receiver_type') - if receiver.is_mut && !p.expr_var.is_mut { - // println('$method_call recv=$receiver.name recv_mut=$receiver.is_mut') - if p.expr_var.is_for_var { - p.error('`$p.expr_var.name` is immutable, `for` variables' + ' always are') - } - else { - p.error('`$p.expr_var.name` is immutable, declare it with `mut`') - } - } - if !p.expr_var.is_changed && receiver.is_mut { - p.mark_var_changed(p.expr_var) - } - if !receiver_is_interface { - p.gen_method_call(receiver, receiver_type, cgen_name, f.typ, method_ph) - } - } - else { - // Normal function call - p.gen('$cgen_name (') - } - // `foo()` - // if f is generic, the name is changed to a suitable instance in dispatch_generic_fn_instance() - // we then replace `cgen_name` with the instance's name - generic := f.is_generic - p.fn_call_args(mut f, generic_param_types) - if generic { - line := if p.cgen.is_tmp { p.cgen.tmp_line } else { p.cgen.cur_line } - p.cgen.resetln(line.replace('$cgen_name (', '$f.name (')) - // println('calling inst $f.name: $p.cgen.cur_line') - } - // if !is_interface { - p.gen(')') - // } - p.calling_c = false - if is_comptime_define { - p.cgen.nogen = false - p.cgen.resetln('') - } - // println('end of fn call typ=$f.typ') -} -// for declaration -// update the Fn object's args[] -fn (p mut Parser) fn_args(f mut Fn) { - p.check(.lpar) - defer { - p.check(.rpar) - } - if f.is_interface { - interface_arg := Var{ - typ: f.receiver_typ - token_idx: p.cur_tok_index() - } - f.args << interface_arg - } - // `(int, string, int)` - // Just register fn arg types - types_only := p.tok == .mul || p.tok == .amp || (p.peek() == .comma && -p.table.known_type(p.lit)) || p.peek() == .rpar // (int, string) - if types_only { - for p.tok != .rpar { - is_mut := p.tok == .key_mut - if is_mut { - p.check(.key_mut) - p.fspace() - } - typ := p.get_type() - if typ == '' { - // && !f.is_c { - if p.prev_tok != .ellipsis { - p.error('bad fn arg type') - } - } - p.check_and_register_used_imported_type(typ) - v := Var{ - typ: typ - is_arg: true - is_mut: is_mut - - line_nr: p.scanner.line_nr - token_idx: p.cur_tok_index() - } - // f.register_var(v) - f.args << v - if p.tok == .comma { - p.next() - p.fspace() - } - } - } - // `(a int, b, c string)` syntax - for p.tok != .rpar { - mut names := [p.check_name()] - // `a,b,c int` syntax - for p.tok == .comma { - p.check(.comma) - p.fspace() - names << p.check_name() - } - p.fspace() - is_mut := p.tok == .key_mut - if is_mut { - p.check(.key_mut) - p.fspace() - } - // variadic arg - if p.tok == .ellipsis { - p.check(.ellipsis) - if p.tok == .rpar { - p.error('you must provide a type for vargs: eg `...string`. multiple types `...` are not supported yet.') - } - f.is_variadic = true - } - mut typ := p.get_type() - if !p.first_pass() && !p.table.known_type(typ) { - p.error('fn_args: unknown type $typ') - } - if f.is_variadic { - if !f.is_c { - // register varg struct, incase function is never called - if p.first_pass() && !f.is_generic { - p.register_vargs_stuct(typ, 0) - } - typ = 'varg_$typ' - } - else { - typ = '...$typ' // TODO: fix, this is invalid in C - } - } - p.check_and_register_used_imported_type(typ) - if is_mut && is_primitive_type(typ) { - p.error('mutable arguments are only allowed for arrays, maps, and structs.' + '\nreturn values instead: `fn foo(n mut int) {` => `fn foo(n int) int {`') - } - for name in names { - if is_mut { - typ += '*' - } - v := Var{ - name: name - typ: typ - is_arg: true - is_mut: is_mut - ptr: is_mut - line_nr: p.scanner.line_nr - token_idx: p.cur_tok_index() - } - p.register_var(v) - f.args << v - } - if p.tok == .comma { - p.check(.comma) - p.fspace() - } - // unnamed (C definition) - if p.tok == .ellipsis { - if !f.is_c { - p.error('variadic argument syntax must be `arg_name ...type` eg `argname ...string`.') - } - f.args << Var{ - // name: '...' - typ: '...' - } - p.next() - } - } - //if types_only && p.peek() == .lcbr { - //println('wtf') - //} -} - -// foo *(1, 2, 3, mut bar)* -fn (p mut Parser) fn_call_args(f mut Fn, generic_param_types []string) { - // println('fn_call_args() name=$f.name args.len=$f.args.len') - // C func. # of args is not known - p.check(.lpar) - if f.is_c { - for p.tok != .rpar { - // C.func(var1, var2.method()) - // If the parameter calls a function or method that is not C, - // the value of p.calling_c is changed - p.calling_c = true - ph := p.cgen.add_placeholder() - typ := p.bool_expression() - // Cast V byteptr to C char* (byte is unsigned in V, that led to C warnings) - if typ == 'byte*' { - p.cgen.set_placeholder(ph, '(char*)') - } - if p.tok == .comma { - p.gen(', ') - p.check(.comma) - p.fspace() - } - } - p.check(.rpar) - return - } - // add debug information to panic when -g arg is passed - if p.v.pref.is_debug && f.name == 'panic' && !p.is_js { - mod_name := p.mod.replace('_dot_', '.') - fn_name := p.cur_fn.name.replace('${p.mod}__', '') - file_path := cescaped_path(p.file_path) - p.cgen.resetln(p.cgen.cur_line.replace('v_panic (', 'panic_debug ($p.scanner.line_nr, tos3("$file_path"), tos3("$mod_name"), tos2((byte *)"$fn_name"), ')) - } - // mut saved_args := []string - mut saved_args := generic_param_types - for i, arg in f.args { - // Receiver is the first arg - // Skip the receiver, because it was already generated in the expression - if i == 0 && f.is_method { - if f.args.len > 1 { - // && !p.is_js { - p.gen(', ') - } - // if f.args[0].typ.ends_with('*') { - // p.gen('&/*119*/') - // } - // pos := p.cgen.cur_line.index('/* ? */') - // if pos > -1 { - // expr := p.cgen.cur_line[pos..] - // // TODO hack - // // If current expression is a func call, generate the array hack - // if expr.contains('(') { - // p.cgen.set_placeholder(pos, '(${arg.typ[..arg.typ.len-1]}[]){') - // p.gen('}[0] ') - // } - // } - continue - } - // Reached the final vararg? Quit - if i == f.args.len - 1 && arg.typ.starts_with('varg_') { - break - } - ph := p.cgen.add_placeholder() - // `)` here means that not enough args were provided - if p.tok == .rpar { - p.error('not enough arguments in call to `${f.str_for_error()}`') - } - // If `arg` is mutable, the caller needs to provide `mut`: - // `mut numbers := [1,2,3]; reverse(mut numbers);` - if arg.is_mut { - if p.tok != .key_mut && p.tok == .name { - p.mutable_arg_error(i, arg, f) - } - if p.peek() != .name { - p.error('`$arg.name` is a mutable argument, you need to ' + 'provide a variable to modify: `${f.name}(... mut a...)`') - } - p.check(.key_mut) - p.fspace() - var_name := p.lit - v := p.find_var(var_name) or { - p.error('`$arg.name` is a mutable argument, you need to ' + 'provide a variable to modify: `${f.name}(... mut a...)`') - exit(1) - } - if !v.is_changed { - p.mark_var_changed(v) - } - } - p.expected_type = arg.typ - clone := p.pref.autofree && p.mod != 'string' && arg.typ == 'string' && !p.builtin_mod // && arg.is_moved - if clone { - p.gen('/*YY f=$f.name arg=$arg.name is_moved=$arg.is_moved*/string_clone(') - } - // x64 println gen - if p.pref.backend == .x64 && i == 0 && f.name == 'println' && p.tok == .string&& p.peek() == .rpar { - //p.x64.gen_print(p.lit) - } - mut typ := p.bool_expression() - // Register an interface type usage: - // fn run(r Animal) { ... } - // `run(dog)` adds `Dog` to the `Animal` interface. - // This is needed to generate an interface table. - if arg.typ.ends_with('er') || arg.typ[0] == `I` { - t := p.table.find_type(arg.typ) - if t.cat == .interface_ { - // NB: here concrete_type_name can be 'Dog' OR 'Dog_ptr' - // cgen should have generated a _I_Dog_to_Speaker conversion function - // C: perform( _I_Dog_to_Speaker((Dog){...}) ) - // In case of _ptr, there is no need for conversion, so the generated - // code will be just: - // C: perform( dog_ptr ) - concrete_type_name := typ.replace('*', '_ptr') - // concrete_type_name here can be say Dog, or ui__Group_ptr (in vui) - //eprintln('arg.typ: $arg.typ | concrete_type_name: $concrete_type_name ') - if !concrete_type_name.ends_with('_ptr') { - p.cgen.set_placeholder(ph, 'I_${concrete_type_name}_to_${arg.typ}(') - p.gen(')') - } - p.table.add_gen_type(arg.typ, typ) - } - } - if clone { - p.gen(')') - } - // Optimize `println`: replace it with `printf` to avoid extra allocations and - // function calls. - // `println(777)` => `printf("%d\n", 777)` - // (If we don't check for void, then V will compile `println(func())`) - if i == 0 && (f.name == 'println' || f.name == 'print') && typ == 'ustring' { - if typ == 'ustring' { - p.gen('.s') - } - typ = 'string' - } - if i == 0 && (f.name == 'println' || f.name == 'print') && !(typ in ['string', 'ustring', 'void']) { - // - tt := p.table.find_type(typ) - $if !windows { - $if !js { - fmt := p.typ_to_fmt(typ, 0) - if fmt != '' && typ != 'bool' { - nl := if f.name == 'println' { '\\n' } else { '' } - p.cgen.resetln(p.cgen.cur_line.replace(f.name + ' (', '/*opt*/printf ("' + fmt + '$nl", ')) - continue - } - } - } - if typ.ends_with('*') { - p.cgen.set_placeholder(ph, 'ptr_str(') - p.gen(')') - continue - } - // Make sure this type has a `str()` method - $if !js { - if !tt.has_method('str') { - // varg - if tt.name.starts_with('varg_') { - p.gen_varg_str(tt) - p.cgen.set_placeholder(ph, '${typ}_str(') - p.gen(')') - continue - } - // Arrays have automatic `str()` methods - else if tt.name.starts_with('array_') { - p.gen_array_str(tt) - p.cgen.set_placeholder(ph, '${typ}_str(') - p.gen(')') - continue - } - // struct - else if tt.cat == .struct_ { - p.gen_struct_str(tt) - p.cgen.set_placeholder(ph, '${typ}_str(') - p.gen(')') - continue - } - else { - base := p.base_type(tt.name) - if base != tt.name { - base_type := p.find_type(base) - if base_type.has_method('str') { - p.cgen.set_placeholder(ph, '${base_type.name}_str(') - p.gen(')') - continue - } - } - } - error_msg := ('`$typ` needs to have method `str() string` to be printable') - p.error(error_msg) - } - p.cgen.set_placeholder(ph, '${typ}_str(') - p.gen(')') - } - continue - } - got := typ - expected := arg.typ - got_ptr := got.ends_with('*') - exp_ptr := expected.ends_with('*') - // println('fn arg got="$got" exp="$expected"') - type_mismatch := !p.check_types_no_throw(got, expected) - if type_mismatch && f.is_generic { - // println("argument `$arg.name` is generic") - saved_args << got - } - else if type_mismatch { - mut j := i - if f.is_method { - j-- - } - mut nr := '${j+1}th' - if j == 0 { - nr = 'first' - } - else if j == 1 { - nr = 'second' - } - else if j == 2 { - nr = 'third' - } - p.error('cannot use type `$typ` as type `$arg.typ` in $nr ' + 'argument to `${f.name}()`') - } - else { - saved_args << '' - } - is_interface := p.table.is_interface(arg.typ) - // Automatically add `&` or `*` before an argument. - // V, unlike C and Go, simplifies this aspect: - // `foo(bar)` is allowed where `foo(&bar)` is expected. - // The argument is not mutable, so it won't be changed by the function. - // It doesn't matter whether it's passed by referencee or by value - // to the end user. - if !is_interface { - // Dereference - if got_ptr && !exp_ptr { - p.cgen.set_placeholder(ph, '*') - } - // Reference - // TODO ptr hacks. DOOM hacks, fix please. - if !got_ptr && exp_ptr && got != 'voidptr' { - // Special case for mutable arrays. We can't `&` function - // results, - // have to use `(array[]){ expr }` hack. - if expected.starts_with('array_') && exp_ptr { - // && !arg.is_mut{ - p.cgen.set_placeholder(ph, '& /*111*/ (array[]){') - p.gen('}[0] ') - } - else if exp_ptr && expected == got + '*' { - $if !tinyc { - expr := p.cgen.cur_line[ph..] - // TODO hack - // If current expression is a func call, generate the array hack - if expr.contains('(') { - // println('fn hack expr=$expr') - p.cgen.set_placeholder(ph, '& /*113 e="$expected" g="$got"*/ ($got[]){') - p.gen('}[0] ') - } - else { - p.cgen.set_placeholder(ph, '& /*114*/') - } - } $else { - p.cgen.set_placeholder(ph, '& /*114*/') - } - } - // println('\ne:"$expected" got:"$got"') - else if !(expected == 'void*' && got == 'int') && !(expected == 'void*' && got == 'byteptr') && !(expected == 'byte*' && got.contains(']byte')) && !(expected == 'byte*' && got == 'string') && - // ! (expected == 'void*' && got == 'array_int') { - !(expected == 'byte*' && got == 'byteptr') && !p.pref.is_bare { - p.cgen.set_placeholder(ph, '& /*112 e="$expected" g="$got" */') - } - } - } - else if is_interface { - if !got_ptr { - // p.cgen.set_placeholder(ph, '&') - } - // Pass all interface methods - // interface_type := p.table.find_type(arg.typ) - // for method in interface_type.methods { - // p.gen(', ${typ}_${method.name} ') - // } - } - // Check for commas - if i < f.args.len - 1 { - // Handle 0 args passed to varargs - if p.tok != .comma && !f.is_variadic { - p.error('wrong number of arguments in call to `${f.str_for_error()}`') - } - if p.tok == .comma && (!f.is_variadic || (f.is_variadic && i < f.args.len - 2)) { - p.check(.comma) - p.fspace() - p.gen(',') - } else if p.tok != .comma { - p.gen(',') - } - } - } - // varargs - varg_type,varg_values := p.fn_call_vargs(f) - if f.is_variadic { - saved_args << varg_type - } - if p.tok == .comma { - p.error('wrong number of arguments in call to `${f.str_for_error()}`') - } - p.check(.rpar) - if f.is_generic && !p.scanner.is_fmt { - type_map := p.extract_type_inst(f, saved_args) - p.dispatch_generic_fn_instance(mut f, type_map) - } - if f.is_variadic { - p.fn_gen_caller_vargs(f, varg_type, varg_values) - } -} - -// From a given generic function and an argument list matching its signature, -// create a type instantiation -fn (p mut Parser) extract_type_inst(f &Fn, args_ []string) TypeInst { - mut r := TypeInst{ - } - mut i := 0 - mut args := args_ - if f.typ != 'void' { - args << f.typ - } - for e in args { - if e == '' { - continue - } - tp := f.type_pars[i] - mut ti := e - if ti.starts_with('fn (') { - fn_args := ti[4..].all_before(') ').split(',') - mut found := false - for fa_ in fn_args { - mut fa := fa_ - for fa.starts_with('array_') { - fa = fa[6..] - } - if fa == tp { - r.inst[tp] = fa - found = true - i++ - break - } - } - if found { - continue - } - ti = ti.all_after(') ') - } - for ti.starts_with('array_') { - ti = ti[6..] - } - if r.inst[tp] != '' { - if r.inst[tp] != ti { - p.error('type parameter `$tp` has type ${r.inst[tp]}, not `$ti`') - } - continue - } - // println("extracted $tp => $ti") - r.inst[tp] = ti - i++ - if i >= f.type_pars.len { - break - } - } - if r.inst[f.typ] == '' && f.typ in f.type_pars { - r.inst[f.typ] = '_ANYTYPE_' - } - for tp in f.type_pars { - if r.inst[tp] == '' { - // p.error_with_token_index('unused type parameter `$tp`', f.body_idx-2) - p.error('unused type parameter `$tp`') - } - } - return r -} - -// replace a generic type using TypeInst -fn replace_generic_type(gen_type string, ti &TypeInst) string { - mut typ := gen_type.replace('map_', '').replace('varg_', '').trim_right('*').replace('ptr_', '') - for typ.starts_with('array_') { - typ = typ[6..] - } - if typ in ti.inst { - typ = gen_type.replace(typ, ti.inst[typ]) - return typ - } - typ = gen_type - if typ.starts_with('fn (') { - args := typ[4..].all_before_last(')').split(',') - ret_t := typ.all_after(')').trim_space() - mut args_r := []string - for arg in args { - args_r << replace_generic_type(arg, ti) - } - mut t := 'fn (' + args_r.join(',') + ')' - if ret_t.len > 0 { - t += ' ' + replace_generic_type(ret_t, ti) - } - typ = t - } - return typ -} - -// replace return type & param types for a given generic function using TypeInst -fn replace_generic_type_params(f mut Fn, ti &TypeInst) { - mut args := []Var - for i, _ in f.args { - mut arg := f.args[i] - arg.typ = replace_generic_type(arg.typ, ti) - args << arg - } - f.args = args - f.typ = replace_generic_type(f.typ, ti) - if f.typ.ends_with('_T') { - par := ti.inst.keys()[0] - f.typ = f.typ + '_' + ti.inst[par] - } -} - -fn (p mut Parser) register_vargs_stuct(typ string, len int) string { - vargs_struct := 'varg_$typ' - varg_type := Type{ - cat: .struct_ - name: vargs_struct - mod: p.mod - } - mut varg_len := len - if !p.table.known_type(vargs_struct) { - p.table.register_type(varg_type) - p.cgen.typedefs << 'typedef struct $vargs_struct $vargs_struct;\n' - } - else { - ex_typ := p.table.find_type(vargs_struct) - ex_len := ex_typ.fields[1].name[5..ex_typ.fields[1].name.len - 1].int() - if ex_len > varg_len { - varg_len = ex_len - } - p.table.rewrite_type(varg_type) - } - p.table.add_field(vargs_struct, 'len', 'int', false, '', .public) - p.table.add_field(vargs_struct, 'args[$varg_len]', typ, false, '', .public) - return vargs_struct -} - -fn (p mut Parser) fn_call_vargs(f Fn) (string,[]string) { - if !f.is_variadic { - return '',[]string - } - last_arg := f.args.last() - // varg_def_type := last_arg.typ[3..] - mut types := []string - mut values := []string - for p.tok != .rpar { - if p.tok == .comma { - p.check(.comma) - } - varg_type,varg_value := p.tmp_expr() - if varg_type.starts_with('varg_') && (values.len > 0 || p.tok == .comma) { - p.error('You cannot pass additional vargs when forwarding vargs to another function/method') - } - if !f.is_generic { - p.check_types(last_arg.typ, varg_type) - } - else { - if types.len > 0 { - for t in types { - p.check_types(varg_type, t) - } - } - } - ref_deref := if last_arg.typ.ends_with('*') && !varg_type.ends_with('*') { '&' } else if !last_arg.typ.ends_with('*') && varg_type.ends_with('*') { '*' } else { '' } - types << varg_type - values << '$ref_deref$varg_value' - } - for va in p.table.varg_access { - if va.fn_name != f.name { - continue - } - if va.index >= values.len { - p.error_with_token_index('variadic arg index out of range: $va.index/${values.len-1}, vargs are 0 indexed', va.tok_idx) - } - } - - if types.len == 0 { - return last_arg.typ,[]string - } - - insert_comma_after_arg := if f.is_method { 2 } else { 1 } - - if f.args.len > insert_comma_after_arg { - p.cgen.gen(',') - } - - return types[0],values -} - -fn (p mut Parser) fn_gen_caller_vargs(f &Fn, varg_type string, values []string) { - is_varg := varg_type.starts_with('varg_') - if is_varg { - // forwarding varg - if values.len == 0 { - vargs_struct := p.register_vargs_stuct(varg_type, 1) - p.cgen.gen('&($vargs_struct){.len=0}') - } else { - p.cgen.gen('${values[0]}') - } - } - else { - vargs_struct := p.register_vargs_stuct(varg_type, values.len) - p.cgen.gen('&($vargs_struct){.len=$values.len,.args={' + values.join(',') + '}}') - } -} - -fn (p mut Parser) register_multi_return_stuct(types []string) string { - typ := '_V_MulRet_' + types.join('_V_').replace('*', '_PTR_') - if p.table.known_type(typ) { - return typ - } - p.table.register_type(Type{ - cat: .struct_ - name: typ - mod: p.mod - }) - for i, t in typ.replace('_V_MulRet_', '').replace('_PTR_', '*').split('_V_') { - p.table.add_field(typ, 'var_$i', t, false, '', .public) - } - p.cgen.typedefs << 'typedef struct $typ $typ;' - return typ -} - -fn rename_generic_fn_instance(f mut Fn, ti &TypeInst) { - f.name = f.name + '_T' - for k in ti.inst.keys() { - f.name = f.name + '_' + type_to_safe_str(ti.inst[k]) - } -} - -fn (p mut Parser) dispatch_generic_fn_instance(f mut Fn, ti &TypeInst) { - mut new_inst := true - for e in f.type_inst { - if e.inst.str() == ti.inst.str() { - new_inst = false - break - } - } - if !new_inst { - rename_generic_fn_instance(mut f, ti) - replace_generic_type_params(mut f, ti) - _ = p.table.find_fn(f.name) or { - p.error('function instance `$f.name` not found') - return - } - // println('using existing inst ${p.fn_signature_v(f)}') - return - } - f.type_inst << *ti - p.table.register_fn(f) - if f.is_method { - f.name = f.receiver_typ + '_' + f.name - } - rename_generic_fn_instance(mut f, ti) - replace_generic_type_params(mut f, ti) - // TODO: Handle case where type not defined yet, see above - // if f.typ in f.type_pars { f.typ = '_ANYTYPE_' } - // if f.typ in ti.inst { - // f.typ = ti.inst[f.typ] - // } - if f.is_method { - // TODO: add_method won't add anything on second pass - // p.add_method(f.args[0].typ.trim_right('*'), f) - } - else { - p.table.register_fn(f) - } - mut gp := p.v.parsers[f.parser_idx] - gp.is_vgen = true - saved_state := p.save_state() - p.clear_state(false, true) - gp.token_idx = f.generic_fn_idx - gp.generic_dispatch = *ti - gp.next() - gp.fn_decl() - gp.generic_dispatch = TypeInst{ - } - p.cgen.lines_extra << p.cgen.lines - p.restore_state(saved_state, false, true) - p.cgen.fns << '${p.fn_signature(f)};' -} - -// "fn (int, string) int" -fn (f &Fn) typ_str() string { - mut sb := strings.new_builder(50) - sb.write('fn (') - for i, arg in f.args { - sb.write(arg.typ) - if i < f.args.len - 1 { - sb.write(',') - } - } - sb.write(')') - if f.typ != 'void' { - sb.write(' $f.typ') - } - return sb.str() -} - -// f.args => "int a, string b" -fn (f &Fn) str_args(table &Table) string { - mut s := '' - for i, arg in f.args { - // Interfaces are a special case. We need to pass the object + pointers - // to all methods: - // fn handle(r Runner) { => - // void handle(void *r, void (*Runner_run)(void*)) { - /* - if table.is_interface(arg.typ) { - // First the object (same name as the interface argument) - s += ' void* $arg.name' - // Now all methods - interface_type := table.find_type(arg.typ) - for method in interface_type.methods { - s += ', $method.typ (*${arg.typ}_${method.name})(void*' - if method.args.len > 1 { - for a in method.args[1..] { - s += ', $a.typ' - } - } - s += ')' - } - } - */ - if arg.typ.starts_with('varg_') { - s += '$arg.typ *$arg.name' - } - else { - // s += '$arg.typ $arg.name' - s += table.cgen_name_type_pair(arg.name, arg.typ) // '$arg.typ $arg.name' - } - if i < f.args.len - 1 { - s += ', ' - } - } - return s -} - - -// f.args => "a, b, c" -fn (f &Fn) str_args_without_types(table &Table) string { - mut res := []string - for arg in f.args { - res << arg.name - } - return res.join(', ') -} - -// find local function variable with closest name to `name` -fn (p &Parser) find_misspelled_local_var(name string, min_match f32) string { - mut closest := f32(0) - mut closest_var := '' - for var in p.local_vars { - if var.scope_level > p.cur_fn.scope_level { - continue - } - n := name.all_after('.') - if var.name == '' || (n.len - var.name.len > 2 || var.name.len - n.len > 2) { - continue - } - c := strings.dice_coefficient(var.name, n) - if c > closest { - closest = c - closest_var = var.name - } - } - return if closest >= min_match { closest_var } else { '' } -} - -fn (fns []Fn) contains(f Fn) bool { - for ff in fns { - if ff.name == f.name { - return true - } - } - return false -} - -fn (p &Parser) fn_signature(f &Fn) string { - return '$f.typ ${f.name}(${f.str_args(p.table)})' -} - -pub fn (f &Fn) v_fn_module() string { - return f.mod -} - -pub fn (f &Fn) v_fn_name() string { - return f.name.replace('${f.mod}__', '') -} - -pub fn (f &Fn) str_for_error() string { - // Build the args for the error - mut s := '' - for i, a in f.args { - if i == 0 { - if f.is_method { - s += a.typ + '.' + f.name + '(' - continue - } - s += f.name + '(' - } - s += a.typ - if i < f.args.len - 1 { - s += ', ' - } - } - return s + ')' -} diff --git a/vlib/compiler/for.v b/vlib/compiler/for.v deleted file mode 100644 index b5735cadc1..0000000000 --- a/vlib/compiler/for.v +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -fn (p mut Parser) for_st() { - p.check(.key_for) - p.for_expr_cnt++ - next_tok := p.peek() - if p.tok != .lcbr { - p.fspace() - } - // debug := p.scanner.file_path.contains('r_draw') - p.open_scope() - //mut label := 0 - mut to := 0 - if p.tok == .lcbr { - // Infinite loop - p.gen('while (1) {') - } - else if p.tok == .key_mut { - p.error('`mut` is not required in for loops') - } - // for i := 0; i < 10; i++ { - else if next_tok == .decl_assign || next_tok == .assign || p.tok == .semicolon { - p.genln('for (') - if next_tok == .decl_assign { - p.check_not_reserved() - p.var_decl() - } - else if p.tok != .semicolon { - // allow `for ;; i++ {` - // Allow `for i = 0; i < ...` - p.statement(false) - } - p.check(.semicolon) - p.gen(' ; ') - p.fspace() - if p.tok != .semicolon { - p.bool_expression() - } - p.check(.semicolon) - p.gen(' ; ') - p.fspace() - if p.tok != .lcbr { - p.statement(false) - } - p.genln(') { ') - } - // for i, val in array - else if p.peek() == .comma { - /* - `for i, val in array {` - ==> - ``` - array_int tmp = array; - for (int i = 0; i < tmp.len; i++) { - int val = tmp[i]; - ``` - */ - i := p.check_name() - p.check(.comma) - p.fspace() - val := p.check_name() - if i == '_' && val == '_' { - p.error('no new variables on the left side of `in`') - } - p.fspace() - p.check(.key_in) - p.fspace() - tmp := p.get_tmp() - mut typ,expr := p.tmp_expr() - is_arr := typ.starts_with('array_') - is_map := typ.starts_with('map_') - is_str := typ == 'string' - is_variadic_arg := typ.starts_with('varg_') - if !is_arr && !is_str && !is_map && !is_variadic_arg { - p.error('cannot range over type `$typ`') - } - if !is_variadic_arg { - if p.is_js { - p.genln('var $tmp = $expr;') - } - else { - p.genln('$typ $tmp = $expr;') - } - } - // typ = strings.Replace(typ, "_ptr", "*", -1) - mut i_var_type := 'int' - if is_variadic_arg { - typ = typ[5..] - p.gen_for_varg_header(i, expr, typ, val) - } - else if is_arr { - typ = parse_pointer(typ[6..]) - p.gen_for_header(i, tmp, typ, val) - } - else if is_map { - i_var_type = 'string' - typ = parse_pointer(typ[4..]) - p.gen_for_map_header(i, tmp, typ, val, typ) - } - else if is_str { - typ = 'byte' - p.gen_for_str_header(i, tmp, typ, val) - } - // Register temp vars - if i != '_' { - if p.known_var(i) { - p.error('redefinition of `$i`') - } - p.register_var(Var{ - name: i - typ: i_var_type - is_mut: true - is_changed: true - }) - } - if val != '_' { - if p.known_var(val) { - p.error('redefinition of `$val`') - } - p.register_var(Var{ - name: val - typ: typ - ptr: typ.contains('*') - }) - } - } - // `for val in vals` - else if p.peek() == .key_in || p.peek() == .left_arrow { - p.check_not_reserved() - val := p.check_name() - p.fspace() - //p.check(.key_in) - p.next() - p.fspace() - tmp := p.get_tmp() - mut typ,expr := p.tmp_expr() - is_range := p.tok == .dotdot - is_variadic_arg := typ.starts_with('varg_') - mut range_end := '' - if is_range { - p.check_types(typ, 'int') - p.check_space(.dotdot) - if p.pref.backend == .x64 { - to = p.lit.int() - } - range_typ,range_expr := p.tmp_expr() - p.check_types(range_typ, 'int') - range_end = range_expr - if p.pref.backend == .x64 { - //label = p.x64.gen_loop_start(expr.int()) - // to = range_expr.int() // TODO why empty? - } - } - is_arr := typ.contains('array') - is_fixed := typ.starts_with('[') - is_str := typ == 'string' - if !is_arr && !is_str && !is_range && !is_fixed && !is_variadic_arg { - p.error('cannot range over type `$typ`') - } - if !is_variadic_arg { - if p.is_js { - p.genln('var $tmp = $expr;') - } - else if !is_fixed { - // Don't copy if it's a fixed array - p.genln('$typ $tmp = $expr;') - } - } - // TODO var_type := if... - i := p.get_tmp() - if is_variadic_arg { - typ = typ[5..] - p.gen_for_varg_header(i, expr, typ, val) - } - else if is_range { - typ = 'int' - p.gen_for_range_header(i, range_end, tmp, typ, val) - } - else if is_arr { - typ = parse_pointer(typ[6..]) // all after `array_` - p.gen_for_header(i, tmp, typ, val) - } - else if is_str { - typ = 'byte' - p.gen_for_str_header(i, tmp, typ, val) - } - else if is_fixed { - typ = typ.all_after(']') - p.gen_for_fixed_header(i, expr, typ, val) - } - // println('for typ=$typ vartyp=$var_typ') - // Register temp var - if val != '_' { - if p.known_var(val) { - p.error('redefinition of `$val`') - } - p.register_var(Var{ - name: val - typ: typ - ptr: typ.contains('*') - is_changed: true - is_mut: false - is_for_var: true - }) - } - } - else { - // `for a < b {` - p.gen('while (') - p.check_types(p.bool_expression(), 'bool') - p.genln(') {') - } - p.fspace() - p.check(.lcbr) - p.genln('') // TODO why is this needed? - p.statements() - p.close_scope() - p.for_expr_cnt-- - p.returns = false // TODO handle loops that are guaranteed to return - //if label > 0 { - //p.x64.gen_loop_end(to, label) - //} -} - diff --git a/vlib/compiler/gen_c.v b/vlib/compiler/gen_c.v deleted file mode 100644 index 14d236665a..0000000000 --- a/vlib/compiler/gen_c.v +++ /dev/null @@ -1,757 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import strings - -const ( - dot_ptr = '->' -) -// returns the type of the new variable -fn (p mut Parser) gen_var_decl(name string, is_static bool) string { - p.is_var_decl = true - mut typ := p.bool_expression() - // mut typ, expr := p.tmp_expr() - p.is_var_decl = false - if typ.starts_with('...') { - typ = typ[3..] - } - // p.gen('/*after expr*/') - // Option check ? or { - or_else := p.tok == .key_orelse - if or_else { - return p.gen_handle_option_or_else(typ, name, 0) - } - gen_name := p.table.var_cgen_name(name) - mut nt_gen := p.table.cgen_name_type_pair(gen_name, typ) - // `foo := C.Foo{}` => `Foo foo;` - if !p.is_empty_c_struct_init && !typ.starts_with('[') { - nt_gen += '=' - } - else if typ.starts_with('[') && typ[typ.len - 1] != `*` { - // a fixed_array initializer, like `v := [1.1, 2.2]!!` - // ... should translate to the following in C `f32 v[2] = {1.1, 2.2};` - initializer := p.cgen.cur_line - if initializer.len > 0 { - p.cgen.resetln(' = {' + initializer.all_after('{')) - } - else if initializer.len == 0 { - p.cgen.resetln(' = { 0 }') - } - } - if is_static { - nt_gen = 'static $nt_gen' - } - // Now that we know the type, prepend it - // `[typ] [name] = bool_expression();` - // p.cgen.prepend_to_statement(nt_gen) - p.cgen.set_placeholder(0, nt_gen) - return typ -} - -fn (p mut Parser) gen_fn_decl(f Fn, typ, str_args string) { - dll_export_linkage := if p.pref.ccompiler == 'msvc' && p.attr == 'live' && p.pref.is_so { '__declspec(dllexport) ' } else if p.attr == 'inline' { 'static inline ' } else { '' } - fn_name_cgen := p.table.fn_gen_name(f) - // str_args := f.str_args(p.table) - - if p.attr == 'live' && p.pref.is_so { - // See fn.v for details about impl_live_ functions - p.genln('$typ impl_live_${fn_name_cgen} ($str_args);') - } - p.genln('$dll_export_linkage$typ $fn_name_cgen ($str_args) {') -} - -// blank identifer assignment `_ = 111` -fn (p mut Parser) gen_blank_identifier_assign() { - //assign_error_tok_idx := p.token_idx - p.check_name() - p.check_space(.assign) - //is_indexer := p.peek() == .lsbr - is_fn_call,next_expr := p.is_expr_fn_call(p.token_idx) - pos := p.cgen.add_placeholder() - expr_tok := p.cur_tok_index() - p.is_var_decl = true - typ := p.bool_expression() - if typ == 'void' { - p.error_with_token_index('${next_expr}() $err_used_as_value', expr_tok) - } - p.is_var_decl = false - //if !is_indexer && !is_fn_call { - //p.error_with_token_index('assigning `$next_expr` to `_` is redundant', assign_error_tok_idx) - //} - // handle or - if p.tok == .key_orelse { - p.gen_handle_option_or_else(typ, '', pos) - } - else { - if is_fn_call { - p.gen(';') - } - else { - p.cgen.resetln('{$typ _ = $p.cgen.cur_line;}') - } - } -} - -fn (p mut Parser) gen_handle_option_or_else(_typ, name string, fn_call_ph int) string { - mut typ := _typ - if !typ.starts_with('Option_') { - p.error('`or` block cannot be applied to non-optional type') - } - is_assign := name.len > 0 - tmp := p.get_tmp() - p.cgen.set_placeholder(fn_call_ph, '$typ $tmp = ') - typ = parse_pointer(typ[7..]) - p.genln(';') - or_tok_idx := p.token_idx - p.fspace() - p.check(.key_orelse) - p.fspace() - p.check(.lcbr) - p.fspace() - p.register_var(Var{ - name: 'err' - typ: 'string' - is_mut: false - is_used: true - }) - p.register_var(Var{ - name: 'errcode' - typ: 'int' - is_mut: false - is_used: true - }) - if is_assign && !name.contains('.') && !p.is_var_decl { - // don't initialize struct fields - p.genln('$typ $name;') - } - p.genln('if (!$tmp .ok) {') - p.genln('string err = $tmp . error;') - p.genln('int errcode = $tmp . ecode;') - last_ph := p.cgen.add_placeholder() - last_typ := p.statements() - if is_assign && last_typ == typ { - // workaround for -g with default optional value - // when p.cgen.line_directives is true an extra - // line is added so we need to account for that - expr_line := if p.cgen.line_directives { p.cgen.lines[p.cgen.lines.len - 3] } else { p.cgen.lines[p.cgen.lines.len - 2] } - last_expr := expr_line[last_ph..] - p.cgen.lines[p.cgen.lines.len - 2] = '' - // same here - if p.cgen.line_directives { - p.cgen.lines[p.cgen.lines.len - 3] = '' - } - p.genln('if ($tmp .ok) {') - p.genln('$name = *($typ*) $tmp . data;') - p.genln('} else {') - p.genln('$name = $last_expr') - p.genln('}') - } - else if is_assign { - p.genln('$name = *($typ*)${tmp}.data;') - } - if !p.returns && last_typ != typ && is_assign && !(p.prev_tok2 in [.key_continue, .key_break]) { - p.error_with_token_index('`or` block must provide a default value or return/exit/continue/break/panic', or_tok_idx) - } - p.returns = false - return typ -} - -// `files := os.ls('.')?` -fn (p mut Parser) gen_handle_question_suffix(f Fn, ph int) string { - if p.cur_fn.name != 'main__main' { - p.error('`func()?` syntax can only be used inside `fn main()` for now') - } - p.check(.question) - tmp := p.get_tmp() - p.cgen.set_placeholder(ph, '$f.typ $tmp = ') - p.genln(';') - p.genln('if (!${tmp}.ok) v_panic(${tmp}.error);') - typ := f.typ[7..] // option_xxx - p.gen('*($typ*) ${tmp}.data;') - return typ -} - -fn types_to_c(types []Type, table &Table) string { - mut sb := strings.new_builder(10) - for t in types { - // if t.cat != .union_ && t.cat != .struct_ && t.cat != .objc_interface { - if !(t.cat in [.union_, .struct_, .objc_interface, .interface_]) { - continue - } - // if is_atomic { - // sb.write('_Atomic ') - // } - if t.cat == .objc_interface { - sb.writeln('@interface $t.name : $t.parent { @public') - } - else { - kind := if t.cat == .union_ { 'union' } else { 'struct' } - sb.writeln('$kind $t.name {') - if t.cat == .interface_ { - sb.writeln('\tvoid* _object;') - sb.writeln('\tint _interface_idx; // int t') - } - } - for field in t.fields { - sb.write('\t') - sb.writeln(table.cgen_name_type_pair(field.name, field.typ) + ';') - } - sb.writeln('};\n') - if t.cat == .objc_interface { - sb.writeln('@end') - } - } - return sb.str() -} - -fn (p mut Parser) index_get(typ string, fn_ph int, cfg IndexConfig) { - // Erase var name we generated earlier: "int a = m, 0" - // "m, 0" gets killed since we need to start from scratch. It's messy. - // "m, 0" is an index expression, save it before deleting and insert later in map_get() - mut index_expr := '' - if p.cgen.is_tmp { - index_expr = p.cgen.tmp_line[fn_ph..] - p.cgen.resetln(p.cgen.tmp_line[..fn_ph]) - } - else { - index_expr = p.cgen.cur_line[fn_ph..] - p.cgen.resetln(p.cgen.cur_line[..fn_ph]) - } - // Can't pass integer literal, because map_get() requires a void* - tmp := p.get_tmp() - tmp_ok := p.get_tmp() - if cfg.is_map { - p.gen('$tmp') - def := type_default(typ) - p.cgen.insert_before('$typ $tmp = $def; ' + 'bool $tmp_ok = map_get(/*$p.file_name : $p.scanner.line_nr*/$index_expr, & $tmp);') - } - else if cfg.is_arr { - if p.pref.translated && !p.builtin_mod { - p.gen('$index_expr ]') - } - else { - ref := if cfg.is_ptr { '*' } else { '' } - if cfg.is_slice { - p.gen(' array_slice2($ref $index_expr) ') - } - else { - p.gen('( *($typ*) array_get($ref $index_expr) )') - } - } - } - else if cfg.is_str && !p.builtin_mod { - if p.pref.is_bare { - p.gen(index_expr) - } - else if cfg.is_slice { - p.gen('string_substr2($index_expr)') - } - else { - p.gen('string_at($index_expr)') - } - } - // Zero the string after map_get() if it's nil, numbers are automatically 0 - // This is ugly, but what can I do without generics? - // TODO what about user types? - if cfg.is_map && typ == 'string' { - // p.cgen.insert_before('if (!${tmp}.str) $tmp = tos("", 0);') - p.cgen.insert_before('if (!$tmp_ok) $tmp = tos((byte *)"", 0);') - } -} - -fn (table mut Table) fn_gen_name(f &Fn) string { - mut name := f.name - if f.is_method { - name = '${f.receiver_typ}_$f.name' - name = name.replace(' ', '') - if f.name.len == 1 { - match f.name[0] { - `+` { - name = name.replace('+', 'op_plus') - } - `-` { - name = name.replace('-', 'op_minus') - } - `*` { - name = name.replace('*', 'op_mul') - } - `/` { - name = name.replace('/', 'op_div') - } - `%` { - name = name.replace('%', 'op_mod') - } - else {} - } - } - } - if f.is_interface { - // iname := f.args[0].typ // Speaker - // var := p.expr_var.name - return '' - } - // Avoid name conflicts (with things like abs(), print() etc). - // Generate v_abs(), v_print() - // TODO duplicate functionality - if f.mod == 'builtin' && f.name in c_reserved { - return 'v_$name' - } - // Obfuscate but skip certain names - // TODO ugly, fix - // NB: the order here is from faster to potentially slower checks - if table.obfuscate && !f.is_c && !(f.name in ['main', 'WinMain', 'main__main', 'gg__vec2', 'build_token_str', 'build_keys']) && !(f.mod in ['builtin', 'darwin', 'os', 'json']) && !f.name.ends_with('_init') && !f.name.contains('window_proc') && !name.ends_with('_str') && !name.contains('contains') { - mut idx := table.obf_ids[name] - // No such function yet, register it - if idx == 0 { - table.fn_cnt++ - table.obf_ids[name] = table.fn_cnt - idx = table.fn_cnt - } - old := name - name = 'f_$idx' - println('$old ==> $name') - } - return name -} - -fn (p mut Parser) gen_method_call(receiver &Var, receiver_type string, cgen_name string, ftyp string, method_ph int) { - // mut cgen_name := p.table.fn_gen_name(f) - mut method_call := cgen_name + ' (' - // 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 ftyp == 'void*' { - if receiver_type.starts_with('array_') { - // array_int => int - cast = parse_pointer(receiver_type.all_after('array_')) - cast = '*($cast*) ' - } - else { - cast = '(voidptr) ' - } - } - p.cgen.set_placeholder(method_ph, '$cast $method_call') -} - -fn (p mut Parser) gen_array_at(typ_ string, is_arr0 bool, fn_ph int) { - mut typ := typ_ - // p.fgen('[') - // array_int a; a[0] - // type is "array_int", need "int" - // typ = typ.replace('array_', '') - // if is_arr0 { - // typ = typ.right(6) - // } - // array a; a.first() voidptr - // type is "array", need "void*" - if typ == 'array' { - typ = 'void*' - } - // No bounds check in translated from C code - if p.pref.translated && !p.builtin_mod { - // Cast void* to typ*: add (typ*) to the beginning of the assignment : - // ((int*)a.data = ... - p.cgen.set_placeholder(fn_ph, '(($typ*)(') - p.gen('.data))[') - } - else { - p.gen(',') - } -} - -fn (p mut Parser) gen_for_header(i, tmp, var_typ, val string) { - p.genln('for (int $i = 0; $i < ${tmp}.len; $i++) {') - if val == '_' { - return - } - p.genln('$var_typ $val = (($var_typ *) $tmp . data)[$i];') -} - -fn (p mut Parser) gen_for_fixed_header(i, tmp, var_typ, val string) { - p.genln('for (int $i = 0; $i < sizeof(${tmp}) / sizeof($tmp [0]); $i++) {') - if val == '_' { - return - } - p.genln('$var_typ $val = $tmp[$i];') -} - -fn (p mut Parser) gen_for_str_header(i, tmp, var_typ, val string) { - // TODO var_typ is always byte - // p.genln('array_byte bytes_$tmp = string_bytes( $tmp );') - p.genln(';\nfor (int $i = 0; $i < $tmp .len; $i ++) {') - if val == '_' { - return - } - // p.genln('$var_typ $val = (($var_typ *) bytes_$tmp . data)[$i];') - p.genln('$var_typ $val = ${tmp}.str[$i];') -} - -fn (p mut Parser) gen_for_range_header(i, range_end, tmp, var_type, val string) { - p.genln(';\nfor (int $i = $tmp; $i < $range_end; $i++) {') - if val == '_' { - return - } - p.genln('$var_type $val = $i;') -} - -fn (p mut Parser) gen_for_map_header(i, tmp, var_typ, val, typ string) { - def := type_default(typ) - p.genln('array_string keys_$tmp = map_keys(& $tmp ); ') - p.genln('for (int l = 0; l < keys_$tmp .len; l++) {') - p.genln('string $i = ((string*)keys_$tmp .data)[l];') - // TODO don't call map_get() for each key, fetch values while traversing - // the tree (replace `map_keys()` above with `map_key_vals()`) - if val == '_' { - return - } - p.genln('$var_typ $val = $def; map_get($tmp, $i, & $val);') -} - -fn (p mut Parser) gen_for_varg_header(i, varg, var_typ, val string) { - p.genln('for (int $i = 0; $i < ${varg}->len; $i++) {') - if val == '_' { - return - } - p.genln('$var_typ $val = (($var_typ *) $varg->args)[$i];') -} - -fn (p mut Parser) gen_array_init(typ string, no_alloc bool, new_arr_ph int, nr_elems int) { - mut new_arr := 'new_array_from_c_array' - if no_alloc { - new_arr += '_no_alloc' - } - if nr_elems == 0 { - p.gen(' TCCSKIP(0) })') - } - else { - p.gen(' })') - } - // Need to do this in the second pass, otherwise it goes to the very top of the out.c file - if !p.first_pass() { - p.cgen.set_placeholder(new_arr_ph, '${new_arr}($nr_elems, $nr_elems, sizeof($typ), EMPTY_ARRAY_OF_ELEMS( $typ, $nr_elems ) { ') - } -} - -fn (p mut Parser) gen_array_set(typ string, is_ptr, is_map bool, fn_ph, assign_pos int, is_cao bool) { - // `a[0] = 7` - // curline right now: `a , 0 = 7` - mut val := p.cgen.cur_line[assign_pos..] - p.cgen.resetln(p.cgen.cur_line[..assign_pos]) - mut cao_tmp := p.cgen.cur_line - mut func := '' - if is_map { - if is_ptr { - func = 'map_set(' - } - else { - func = 'map_set(&' - } - // CAO on map is a bit more complicated as it loads - // the value inside a pointer instead of returning it. - } - else { - if is_ptr { - func = 'array_set(' - if is_cao { - cao_tmp = '*($p.expected_type *) array_get(*$cao_tmp)' - } - } - else { - func = 'array_set(&/*q*/' - if is_cao { - cao_tmp = '*($p.expected_type *) array_get($cao_tmp)' - } - } - } - p.cgen.set_placeholder(fn_ph, func) - if is_cao { - val = cao_tmp + val.all_before('=') + val.all_after('=') - } - p.gen(', & ($typ []) { $val })') -} - -// returns true in case of an early return -fn (p mut Parser) gen_struct_init(typ string, t &Type) bool { - // 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 p.is_c_struct_init { - if t.cat != .c_typedef { - p.cgen.insert_before('struct /*c struct init*/') - } - } - // TODO tm struct struct bug - if typ == 'tm' { - p.cgen.lines[p.cgen.lines.len - 1] = '' - } - mut is_config := false - if p.tok != .lcbr { - p.next() - } else { - is_config = true - } - p.check(.lcbr) - // Handle empty config ({}) - if is_config && p.tok == .rcbr { - p.check(.rcbr) - p.gen('($typ) {EMPTY_STRUCT_INITIALIZATION}') - return true - } - ptr := typ.contains('*') - // `user := User{foo:bar}` => `User user = (User){ .foo = bar}` - if !ptr { - if p.is_c_struct_init { - // `face := C.FT_Face{}` => `FT_Face face;` - if p.tok == .rcbr { - p.is_empty_c_struct_init = true - p.check(.rcbr) - return true - } - p.gen('(struct $typ) {') - p.is_c_struct_init = false - } - else { - p.gen('($typ) {') - } - } - else { - if p.tok == .not { - // old &User{!} ==> 0 hack - p.error('use `${t.name}(0)` instead of `&$t.name{!}`') - /* - p.next() - p.gen('0') - p.check(.rcbr) - return true - */ - - } - p.gen('($t.name*)memdup(&($t.name) {') - } - return false -} - -fn (p mut Parser) gen_struct_field_init(field string) { - p.gen('.$field = ') -} - -fn (p mut Parser) gen_empty_map(typ string) { - p.gen('new_map(1, sizeof($typ))') -} - -fn (p mut Parser) cast(typ string) { - //p.error('old cast syntax') - p.gen('(') - defer { - p.gen(')') - } - p.next() - pos := p.cgen.add_placeholder() - if p.tok == .rpar { - // skip `)` if it's `(*int)(ptr)`, not `int(a)` - p.ptr_cast = true - p.next() - } - p.check(.lpar) - p.expected_type = typ - expr_typ := p.bool_expression() - // Do not allow `int(my_int)` - if expr_typ == typ { - p.warn('casting `$typ` to `$expr_typ` is not needed') - } - // `face := FT_Face(cobj)` => `FT_Face face = *((FT_Face*)cobj);` - casting_voidptr_to_value := expr_typ == 'void*' && !(typ in ['int', 'byteptr']) && !typ.ends_with('*') - p.expected_type = '' - // `string(buffer)` => `tos2(buffer)` - // `string(buffer, len)` => `tos(buffer, len)` - // `string(bytes_array, len)` => `tos(bytes_array.data, len)` - is_byteptr := expr_typ in ['byte*', 'byteptr'] - is_bytearr := expr_typ == 'array_byte' - if typ == 'string' { - if is_byteptr || is_bytearr { - if p.tok == .comma { - p.check(.comma) - p.cgen.set_placeholder(pos, 'tos((byte *)') - if is_bytearr { - p.gen('.data') - } - p.gen(', ') - p.check_types(p.expression(), 'int') - } - else { - if is_bytearr { - p.gen('.data') - } - p.cgen.set_placeholder(pos, 'tos2((byte *)') - } - } - // `string(234)` => error - else if expr_typ == 'int' { - p.error('cannot cast `$expr_typ` to `$typ`, use `str()` method instead') - } - else { - p.error('cannot cast `$expr_typ` to `$typ`') - } - } - else if typ == 'byte' && expr_typ == 'string' { - p.error('cannot cast `$expr_typ` to `$typ`, use backquotes `` to create a `$typ` or access the value of an index of `$expr_typ` using []') - } - else if casting_voidptr_to_value { - p.cgen.set_placeholder(pos, '($typ)(') - } - else { - // Nothing can be cast to bool - if typ == 'bool' { - if is_number_type(expr_typ) { - p.error('cannot cast a number to `bool`') - } - p.error('cannot cast `$expr_typ` to `bool`') - } - // Strings can't be cast - if expr_typ == 'string' { - if is_number_type(typ) { - p.error('cannot cast `string` to `$typ`, use `${expr_typ}.${typ}()` instead') - } - p.error('cannot cast `$expr_typ` to `$typ`') - } - // Nothing can be cast to bool - if expr_typ == 'bool' { - p.error('cannot cast `bool` to `$typ`') - } - if typ != expr_typ && typ in p.table.sum_types { - tt := p.table.find_type(typ) - if expr_typ in tt.ctype_names { - // There is no need for a cast here, since it was already done - // in p.bool_expression, SUM TYPE CAST2 . Besides, doubling the - // cast here causes MSVC to complain with: - // error C2440: 'type cast': cannot convert from 'ExprType' to 'ExprType' - p.cgen.set_placeholder(pos, '(') - }else{ - p.warn('only $tt.ctype_names can be casted to `$typ`') - p.error('cannot cast `$expr_typ` to `$typ`') - } - }else{ - p.cgen.set_placeholder(pos, '($typ)(') - } - } - p.check(.rpar) - p.gen(')') -} - -fn type_default(typ string) string { - if typ.starts_with('array_') { - return 'new_array(0, 1, sizeof( ${parse_pointer(typ[6..])} ))' - } - // Always set pointers to 0 - if typ.ends_with('*') { - return '0' - } - // User struct defined in another module. - if typ.contains('__') { - return '{0}' - } - if typ.ends_with('Fn') { // TODO - return '0' - } - // Default values for other types are not needed because of mandatory initialization - match typ { - 'bool' { - return '0' - } - 'string' { - return 'tos3("")' - } - 'i8' { - return '0' - } - 'i16' { - return '0' - } - 'i64' { - return '0' - } - 'u16' { - return '0' - } - 'u32' { - return '0' - } - 'u64' { - return '0' - } - 'byte' { - return '0' - } - 'int' { - return '0' - } - 'rune' { - return '0' - } - 'f32' { - return '0.0' - } - 'f64' { - return '0.0' - } - 'byteptr' { - return '0' - } - 'voidptr' { - return '0' - } - else {} - } - return '{0}' - // TODO this results in - // error: expected a field designator, such as '.field = 4' - // - Empty ee= (Empty) { . = {0} } ; - /* - return match typ { - 'bool'{ '0'} - 'string'{ 'tos3("")'} - 'i8'{ '0'} - 'i16'{ '0'} - 'i64'{ '0'} - 'u16'{ '0'} - 'u32'{ '0'} - 'u64'{ '0'} - 'byte'{ '0'} - 'int'{ '0'} - 'rune'{ '0'} - 'f32'{ '0.0'} - 'f64'{ '0.0'} - 'byteptr'{ '0'} - 'voidptr'{ '0'} - else { '{0} '} - } - */ - -} - -fn (p mut Parser) gen_array_push(ph int, typ, expr_type, tmp, elm_type string) { - // Two arrays of the same type? - push_array := typ == expr_type - if push_array { - p.cgen.set_placeholder(ph, '_PUSH_MANY(&') - p.gen('), $tmp, $typ)') - } - else { - p.check_types(expr_type, elm_type) - // Pass tmp var info to the _PUSH macro - // Prepend tmp initialisation and push call - // Don't dereference if it's already a mutable array argument (`fn foo(mut []int)`) - push_call := if typ.contains('*') { '_PUSH(' } else { '_PUSH(&' } - p.cgen.set_placeholder(ph, push_call) - p.gen('), $tmp, $elm_type)') - } -} diff --git a/vlib/compiler/gen_js.v b/vlib/compiler/gen_js.v deleted file mode 100644 index d7418d1a21..0000000000 --- a/vlib/compiler/gen_js.v +++ /dev/null @@ -1,260 +0,0 @@ -module compiler - -import strings - -const ( - dot_ptr = '.' -) - -fn (p mut Parser) gen_var_decl(name string, is_static bool) string { - p.gen('var $name /* typ */ = ') - mut typ := p.bool_expression() - if typ.starts_with('...') { typ = typ[3..] } - or_else := p.tok == .key_orelse - if or_else { - // return p.gen_handle_option_or_else(typ, name, pos) - } - return typ -} - -fn (p mut Parser) gen_fn_decl(f Fn, typ, _str_args string) { - mut str_args := '' - for i, arg in f.args { - str_args += ' /** @type { $arg.typ } **/ ' + arg.name - if i < f.args.len - 1 { - str_args += ', ' - } - } - name := p.table.fn_gen_name(f) - if f.is_method { - //p.genln('\n${f.receiver_typ}.prototype.${name} = function($str_args) {') - p.genln('function ${f.receiver_typ}_$name($str_args) {') - } else { - p.genln('/** @return { $typ } **/\nfunction $name($str_args) {') - } -} - -fn (p mut Parser) gen_blank_identifier_assign() { - assign_error_tok_idx := p.token_idx - p.check_name() - p.check_space(.assign) - is_indexer := p.peek() == .lsbr - is_fn_call, next_expr := p.is_expr_fn_call(p.token_idx) - p.bool_expression() - if !is_indexer && !is_fn_call { - p.error_with_token_index('assigning `$next_expr` to `_` is redundant', assign_error_tok_idx) - } - or_else := p.tok == .key_orelse - if or_else { - // return p.gen_handle_option_or_else(typ, '', pos) - } -} - -// TODO: optionals -fn (p mut Parser) gen_handle_option_or_else(_typ, name string, fn_call_ph int) string { - return _typ -} - -fn types_to_c(types []Type, table &Table) string { - mut sb := strings.new_builder(10) - for t in types { - if t.cat != .union_ && t.cat != .struct_ { - continue - } - sb.write('\n/**\n') - sb.write('* @typedef { object } $t.name' + 'Type\n') - for field in t.fields { - sb.writeln('* @property { $field.typ' + '= } $field.name') - } - sb.writeln('**/\n') - sb.writeln('/** @type { function & $t.name' + 'Type } **/') - sb.writeln('var $t.name = function() {}') - } - return sb.str() -} - -fn (p mut Parser) index_get(typ string, fn_ph int, cfg IndexConfig) { - p.cgen.cur_line = p.cgen.cur_line.replace(',', '[') + ']' -} - -fn (table &Table) fn_gen_name(f &Fn) string { - mut name := f.name - if f.is_method { - name = name.replace(' ', '') - name = name.replace('*', '') - name = name.replace('+', 'plus') - name = name.replace('-', 'minus') - return name - } - // Avoid name conflicts (with things like abs(), print() etc). - // Generate b_abs(), b_print() - // TODO duplicate functionality - if f.mod == 'builtin' && f.name in CReserved { - return 'v_$name' - } - return name -} - -//fn (p mut Parser) gen_method_call(receiver &Var, receiver_type string, - //ftyp string, cgen_name string, receiver Var,method_ph int) -fn (p mut Parser) gen_method_call(receiver &Var, receiver_type string, - cgen_name string, ftyp string, method_ph int) -{ - // TODO js methods have been broken from the start - - //mut cgen_name := p.table.fn_gen_name(f) - //mut method_call := cgen_name + '(' - //p.gen('/*2*/.' + cgen_name.all_after('_') + '(') - t := receiver_type.replace('*', '') - p.cgen.set_placeholder(method_ph, '${t}_$cgen_name(') - //p.cgen.set_placeholder(method_ph, '$cast kKE $method_call') - //return method_call -} - - -fn (p mut Parser) gen_array_at(typ string, is_arr0 bool, fn_ph int) { - p.gen('[') -} - -fn (p mut Parser) gen_for_header(i, tmp, var_typ, val string) { - p.genln('for (var $i = 0; $i < ${tmp}.length; $i++) {') - if val == '_' { return } - p.genln('var $val = $tmp [$i];') -} - -fn (p mut Parser) gen_for_range_header(i, range_end, tmp, var_type, val string) { - p.genln(';\nfor (var $i = $tmp; $i < $range_end; $i++) {') - if val == '_' { return } - p.genln('var /*$var_type*/ $val = $i;') -} - -fn (p mut Parser) gen_for_str_header(i, tmp, var_typ, val string) { - p.genln('for (var $i = 0; $i < $tmp .length; $i ++) {') - if val == '_' { return } - p.genln('var $val = $tmp[$i];') -} - -fn (p mut Parser) gen_for_map_header(i, tmp, var_typ, val, typ string) { - p.genln('for (var $i in $tmp) {') - if val == '_' { return } - p.genln('var $val = $tmp[$i];') -} - -fn (p mut Parser) gen_for_varg_header(i, varg, var_typ, val string) { - p.genln('for (var $i = 0; $i < ${varg}.len; $i++) {') - if val == '_' { return } - p.genln('var $val = ${varg}.args[$i];') -} - -fn (p mut Parser) gen_array_init(typ string, no_alloc bool, new_arr_ph int, nr_elems int) { - p.cgen.set_placeholder(new_arr_ph, '[') - p.gen(']') -} - -fn (p mut Parser) gen_array_set(typ string, is_ptr, is_map bool,fn_ph, assign_pos int, is_cao bool) { - mut val := p.cgen.cur_line[assign_pos..] - p.cgen.resetln(p.cgen.cur_line[..assign_pos]) - p.gen('] =') - cao_tmp := p.cgen.cur_line - if is_cao { - val = cao_tmp + val.all_before('=') + val.all_after('=') - } - p.gen(val) -} - -// returns true in case of an early return -fn (p mut Parser) gen_struct_init(typ string, t &Type) bool { - p.next() - p.check(.lcbr) - ptr := typ.contains('*') - if !ptr { - p.gen('{') - } - else { - // TODO tmp hack for 0 pointers init - // &User{!} ==> 0 - if p.tok == .not { - p.next() - p.gen('}') - p.check(.rcbr) - return true - } - } - return false -} - -fn (p mut Parser) gen_struct_field_init(field string) { - p.gen('$field : ') -} - -fn (p mut Parser) gen_empty_map(typ string) { - p.gen('{}') -} - -fn (p mut Parser) cast(typ string) string { - p.next() - pos := p.cgen.add_placeholder() - if p.tok == .rpar { - p.next() - } - p.check(.lpar) - p.bool_expression() - if typ == 'string' { - if p.tok == .comma { - p.check(.comma) - p.cgen.set_placeholder(pos, 'tos(') - //p.gen('tos(') - p.gen(', ') - p.expression() - p.gen(')') - } - } - p.check(.rpar) - return typ -} - -fn type_default(typ string) string { - if typ.starts_with('array_') { - return '[]' - } - // Always set pointers to 0 - if typ.ends_with('*') { - return '0' - } - // User struct defined in another module. - if typ.contains('__') { - return '{}' - } - // Default values for other types are not needed because of mandatory initialization - match typ { - 'bool'{ return '0'} - 'string'{ return 'tos("")'} - 'i8'{ return '0'} - 'i16'{ return '0'} - 'i64'{ return '0'} - 'u16'{ return '0'} - 'u32'{ return '0'} - 'u64'{ return '0'} - 'byte'{ return '0'} - 'int'{ return '0'} - 'rune'{ return '0'} - 'f32'{ return '0.0'} - 'f64'{ return '0.0'} - 'byteptr'{ return '0'} - 'voidptr'{ return '0'} - } - return '{}' -} - -fn (p mut Parser) gen_array_push(ph int, typ, expr_type, tmp, tmp_typ string) { - push_array := typ == expr_type - if push_array { - p.cgen.set_placeholder(ph, 'push(&' ) - p.gen('), $tmp, $typ)') - } else { - p.check_types(expr_type, tmp_typ) - p.gen(')') - p.cgen.cur_line = p.cgen.cur_line.replace(',', '.push') - } -} - diff --git a/vlib/compiler/gen_x64.v b/vlib/compiler/gen_x64.v deleted file mode 100644 index 8f0b6f4670..0000000000 --- a/vlib/compiler/gen_x64.v +++ /dev/null @@ -1,3 +0,0 @@ -module compiler -// import os -// import compiler.x64 diff --git a/vlib/compiler/get_type.v b/vlib/compiler/get_type.v deleted file mode 100644 index 42b979fa46..0000000000 --- a/vlib/compiler/get_type.v +++ /dev/null @@ -1,255 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import ( - strings -) - -fn (p mut Parser) get_type2() Type { - mut mul := false - mut nr_muls := 0 - mut typ := '' - cat := TypeCategory.struct_ - // fn type - if p.tok == .key_fn { - mut f := Fn{ - name: '_' - mod: p.mod - } - p.next() - line_nr := p.scanner.line_nr - p.fn_args(mut f) - // Same line, it's a return type - if p.scanner.line_nr == line_nr { - if p.tok in [.name, .mul, .amp, .lsbr, .question, .lpar] { - f.typ = p.get_type() - } - else { - f.typ = 'void' - } - // println('fn return typ=$f.typ') - } - else { - f.typ = 'void' - } - // Register anon fn type - fn_typ := Type{ - name: f.typ_str() // 'fn (int, int) string' - - mod: p.mod - func: f - cat: .func - } - p.table.register_type(fn_typ) - return fn_typ - } - is_question := p.tok == .question - if is_question { - p.check(.question) - } - - // multiple returns - if p.tok == .lpar { - // p.warn('`()` are no longer necessary in multiple returns' + - // '\nuse `fn foo() int, int {` instead of `fn foo() (int, int) {`') - // if p.inside_tuple {p.error('unexpected (')} - // p.inside_tuple = true - p.check(.lpar) - mut types := []string - for { - types << p.get_type() - if p.tok != .comma { - break - } - p.check(.comma) - p.fspace() - } - p.check(.rpar) - // p.inside_tuple = false - typ = p.register_multi_return_stuct(types) - if is_question { - typ = stringify_pointer(typ) - typ = 'Option_$typ' - p.table.register_type_with_parent(typ, 'Option') - } - return Type{ - name: typ - mod: p.mod - cat: cat - } - } - - // arrays ([]int) - mut arr_level := 0 - for p.tok == .lsbr { - p.check(.lsbr) - // [10]int - if p.tok == .number || (p.tok == .name && !p.inside_const) { - if p.tok == .name { - typ += '[${p.mod}__$p.lit]' - } - else { - typ += '[$p.lit]' - } - p.next() - } - else { - arr_level++ - } - p.check(.rsbr) - } - // map[string]int - if !p.builtin_mod && p.tok == .name && p.lit == 'map' { - p.next() - p.check(.lsbr) - key_type := p.check_name() - if key_type != 'string' { - p.error('maps only support string keys for now') - } - p.check(.rsbr) - val_type := stringify_pointer(p.get_type()) // p.check_name() - typ = 'map_$val_type' - p.register_map(typ) - return Type{ - name: typ - } - } - // ptr/ref - mut warn := false - for p.tok == .mul { - if p.first_pass() { - warn = true - } - mul = true - nr_muls++ - p.check(.mul) - } - if p.tok == .amp { - mul = true - nr_muls++ - p.check(.amp) - } - // generic type check - ti := p.generic_dispatch.inst - if p.lit in ti.keys() { - typ += ti[p.lit] - } - else { - typ += p.lit - } - // C.Struct import - if p.lit == 'C' && p.peek() == .dot { - p.next() - p.check(.dot) - typ = p.lit - } - else { - if warn && p.mod != 'ui' { - p.warn('use `&Foo` instead of `*Foo`') - } - // Module specified? (e.g. gx.Image) - if p.peek() == .dot { - // try resolve full submodule - if !p.builtin_mod && p.import_table.known_alias(typ) { - mod := p.import_table.resolve_alias(typ) - typ = if mod.contains('.') { - mod_gen_name(mod) - } else { - mod - } - } - p.next() - p.check(.dot) - typ += '__$p.lit' - } - mut t := p.table.find_type(typ) - // "typ" not found? try "mod__typ" - if t.name == '' && !p.builtin_mod { - // && !p.first_pass() { - if !typ.contains('array_') && p.mod != 'main' && !typ.contains('__') && !typ.starts_with('[') { - typ = p.prepend_mod(typ) - } - t = p.table.find_type(typ) - 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 { - // println(q.name) - // } - mut t_suggest,tc_suggest := p.table.find_misspelled_type(typ, p, 0.50) - if t_suggest.len > 0 { - t_suggest = '. did you mean: ($tc_suggest) `$t_suggest`' - } - econtext := if p.pref.is_debug { '('+@FILE+':'+@LINE+')' } else {''} - p.error('unknown type `$typ`$t_suggest $econtext') - } - } - else if !t.is_public && t.mod != p.mod && !p.is_vgen && t.name != '' && !p.first_pass() { - p.error('type `$t.name` is private') - } - } - if typ == 'void' { - p.error('unknown type `$typ`') - } - if mul { - typ += strings.repeat(`*`, nr_muls) - } - // Register an []array type - if arr_level > 0 { - // p.log('ARR TYPE="$typ" run=$p.pass') - // We come across "[]User" etc ? - typ = stringify_pointer(typ) - for i := 0; i < arr_level; i++ { - typ = 'array_$typ' - } - p.register_array(typ) - } - p.next() - if is_question { - typ = stringify_pointer(typ) - typ = 'Option_$typ' - p.table.register_type_with_parent(typ, 'Option') - } - // Because the code uses * to see if it's a pointer - if typ == 'byteptr' { - typ = 'byte*' - } - if typ == 'voidptr' { - // if !p.builtin_mod && p.mod != 'os' && p.mod != 'gx' && p.mod != 'gg' && !p.pref.translated { - // p.error('voidptr can only be used in unsafe code') - // } - typ = 'void*' - } - /* - TODO this is not needed? - if typ.last_index('__') > typ.index('__') { - p.error('2 __ in gettype(): typ="$typ"') - } - */ - - return Type{ - name: typ - cat: cat - } -} - -fn parse_pointer(_typ string) string { - if !_typ.starts_with('ptr_') { - return _typ - } - mut typ := _typ.clone() - for typ.starts_with('ptr_') { - typ = typ[4..] + '*' - } - return typ -} - -fn stringify_pointer(typ string) string { - if !typ.ends_with('*') { - return typ - } - count := typ.count('*') - return 'ptr_'.repeat(count) + typ.trim_right('*') -} diff --git a/vlib/compiler/if_match.v b/vlib/compiler/if_match.v deleted file mode 100644 index 3f0a6004c0..0000000000 --- a/vlib/compiler/if_match.v +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import ( - strings -) -// Returns type if used as expression - - -fn (p mut Parser) match_statement(is_expr bool) string { - p.check(.key_match) - p.fspace() - is_mut := p.tok == .key_mut - if is_mut { - p.next() - p.fspace() - } - typ,expr := p.tmp_expr() - if typ.starts_with('array_') { - p.error('arrays cannot be compared') - } - is_sum_type := typ in p.table.sum_types - mut sum_child_type := '' - // is it safe to use p.cgen.insert_before ??? - tmp_var := p.get_tmp() - p.cgen.insert_before('$typ $tmp_var = $expr;') - p.fspace() - p.check(.lcbr) - mut i := 0 - mut all_cases_return := true - // stores typ of resulting variable - mut res_typ := '' - defer { - p.check(.rcbr) - } - for p.tok != .rcbr { - if p.tok == .key_else { - p.check(.key_else) - if p.tok == .arrow { - p.error(warn_match_arrow) - } - // unwrap match if there is only else - if i == 0 { - p.fspace() - if is_expr { - // statements are dissallowed (if match is expression) so user cant declare variables there and so on - // allow braces is else - got_brace := p.tok == .lcbr - if got_brace { - p.fspace() - p.check(.lcbr) - } - p.gen('( ') - res_typ = p.bool_expression() - p.gen(' )') - // allow braces in else - if got_brace { - p.check(.rcbr) - } - return res_typ - } - else { - p.returns = false - p.check(.lcbr) - p.genln('{ ') - p.statements() - p.returns = all_cases_return && p.returns - return '' - } - } - if is_expr { - // statements are dissallowed (if match is expression) so - // user cant declare variables there and so on - p.gen(':(') - // allow braces is else - got_brace := p.tok == .lcbr - if got_brace { - p.fspace() - p.check(.lcbr) - } - p.check_types(p.bool_expression(), res_typ) - // allow braces in else - if got_brace { - p.check(.rcbr) - } - p.gen(strings.repeat(`)`, i + 1)) - return res_typ - } - else { - p.returns = false - p.genln('else // default:') - p.fspace() - p.check(.lcbr) - p.genln('{ ') - p.statements() - p.returns = all_cases_return && p.returns - return '' - } - } - if i > 0 { - if is_expr { - p.gen(': (') - } - else { - p.gen('else ') - } - } - else if is_expr { - p.gen('(') - } - if is_expr { - p.gen('(') - } - else { - p.gen('if (') - } - ph := p.cgen.add_placeholder() - // Multiple checks separated by comma - p.open_scope() - mut got_comma := false - for { - if got_comma { - p.gen(') || (') - } - mut got_string := false - if typ == 'string' { - got_string = true - p.gen('string_eq($tmp_var, ') - } - else if is_sum_type { - p.gen('${tmp_var}.typ == ') - } - else { - p.gen('$tmp_var == ') - } - p.expected_type = typ - // `match node { ast.BoolExpr { it := node as BoolExpr ... } }` - if is_sum_type { - sum_child_type = p.get_type2().name - tt := sum_child_type.all_after('_') - p.gen('SumType_${typ}_$tt') - // println('got child $sum_child_type') - p.register_var(Var{ - name: 'it' - typ: sum_child_type+'*' - is_mut: is_mut - ptr: true - }) - } - else { - p.check_types(p.bool_expression(), typ) - } - p.expected_type = '' - if got_string { - p.gen(')') - } - if p.tok != .comma { - if got_comma { - p.gen(') ') - p.cgen.set_placeholder(ph, '(') - } - break - } - p.check(.comma) - p.fspace() - got_comma = true - } - p.gen(')') - if p.tok == .arrow { - p.error(warn_match_arrow) - p.check(.arrow) - } - // statements are dissallowed (if match is expression) so user cant declare variables there and so on - if is_expr { - p.gen('? (') - // braces are required for now - p.check(.lcbr) - if i == 0 { - // on the first iteration we set value of res_typ - res_typ = p.bool_expression() - } - else { - // later on we check that the value is of res_typ type - p.check_types(p.bool_expression(), res_typ) - } - // braces are required for now - p.fgen_nl() - p.check(.rcbr) - p.gen(')') - } - else { - p.returns = false - p.fspace() - p.check(.lcbr) - p.genln('{ ') - if is_sum_type { - //p.genln(' $sum_child_type it = *($sum_child_type*)$tmp_var .obj ;') - p.genln(' $sum_child_type* it = ($sum_child_type*)${tmp_var}.obj ;') - } - p.statements() - all_cases_return = all_cases_return && p.returns - // p.gen(')') - } - i++ - p.fgen_nl() - p.close_scope() - } - p.error('match must be exhaustive') - // p.returns = false // only get here when no default, so return is not guaranteed - return '' -} - -fn (p mut Parser) switch_statement() { - p.error('`switch` statement has been removed, use `match` instead:\n' + 'https://vlang.io/docs#match') -} - -fn (p mut Parser) if_statement(is_expr bool, elif_depth int) string { - if is_expr { - // if p.fileis('if_expr') { - // println('IF EXPR') - // } - p.inside_if_expr = true - p.gen('((') - } - else { - p.gen('if (') - } - p.next() - p.fspace() - if p.tok == .name && p.peek() == .assign { - p.error('cannot assign on if-else statement') - } - if p.tok == .name && (p.peek() == .inc || p.peek() == .dec) { - p.error('`${p.peek().str()}` is a statement') - } - // `if a := opt() { }` syntax - if p.tok == .name && p.peek() == .decl_assign { - p.check_not_reserved() - option_tmp := p.get_tmp() - var_name := p.lit - if p.known_var(var_name) { - p.error('redefinition of `$var_name`') - } - p.open_scope() - p.next() - p.fspace() - p.check(.decl_assign) - p.fspace() - p.is_var_decl = true - option_type,expr := p.tmp_expr() // := p.bool_expression() - if !option_type.starts_with('Option_') { - p.error('`if x := opt() {` syntax requires a function that returns an optional value') - } - p.is_var_decl = false - typ := parse_pointer(option_type[7..]) - // Option_User tmp = get_user(1); - // if (tmp.ok) { - // User user = *(User*)tmp.data; - // [statements] - // } - p.cgen.insert_before('$option_type $option_tmp = $expr; ') - p.fspace() - p.check(.lcbr) - p.genln(option_tmp + '.ok) {') - p.genln('$typ $var_name = *($typ*) $option_tmp . data;') - p.register_var(Var{ - name: var_name - typ: typ - is_mut: false // TODO - - is_used: true // TODO - // is_alloc: p.is_alloc || typ.starts_with('array_') - // line_nr: p.tokens[ var_token_idx ].line_nr - // token_idx: var_token_idx - - }) - p.statements() - p.close_scope() - p.returns = false - if p.tok == .key_else { - p.next() - p.genln('else {') - p.check(.lcbr) - p.statements() - } - return 'void' - } - else { - p.check_types(p.bool_expression(), 'bool') - } - if is_expr { - p.gen(') ? (') - } - else { - p.genln(') {') - } - p.fspace() - p.check(.lcbr) - if p.inside_if_expr { - p.fspace() - } - mut typ := '' - // if { if hack - if p.tok == .key_if && p.inside_if_expr { - typ = p.factor() - p.next() - } - else { - typ = p.statements() - } - if_returns := p.returns - p.returns = false - if p.tok == .key_else { - if p.inside_if_expr { - p.fspace() - } - else { - p.fgen_nl() - } - p.check(.key_else) - p.fspace() - if p.tok == .key_if { - if is_expr { - p.gen(') : (') - nested := p.if_statement(is_expr, elif_depth + 1) - nested_returns := p.returns - p.returns = if_returns && nested_returns - return nested - } - else { - p.gen(' else ') - nested := p.if_statement(is_expr, 0) - nested_returns := p.returns - p.returns = if_returns && nested_returns - return nested - } - // return '' - } - if is_expr { - p.gen(') : (') - } - else { - p.genln(' else { ') - } - p.check(.lcbr) - if is_expr { - p.fspace() - } - // statements() returns the type of the last statement - first_typ := typ - typ = p.statements() - p.inside_if_expr = false - if is_expr { - p.check_types(first_typ, typ) - p.gen(strings.repeat(`)`, 2 * (elif_depth + 1))) - } - else_returns := p.returns - p.returns = if_returns && else_returns - return typ - } - p.inside_if_expr = false - if p.fileis('test_test') { - println('if ret typ="$typ" line=$p.scanner.line_nr') - } - return typ -} diff --git a/vlib/compiler/json_gen.v b/vlib/compiler/json_gen.v deleted file mode 100644 index 5dee0495a4..0000000000 --- a/vlib/compiler/json_gen.v +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler -// TODO replace with comptime code generation. -// TODO remove cJSON dependency. -// OLD: User decode_User(string js) { -// now it's -// User decode_User(cJSON* root) { -// User res; -// res.name = decode_string(js_get(root, "name")); -// res.profile = decode_Profile(js_get(root, "profile")); -// return res; -// } -// Codegen json_decode/encode funcs -fn (p mut Parser) gen_json_for_type(typ Type) { - mut dec := '' - mut enc := '' - t := typ.name - if t == 'int' || t == 'string' || t == 'bool' { - return - } - if p.first_pass() { - return - } - // println('gen_json_for_type( $typ.name )') - // Register decoder fn - mut dec_fn := Fn{ - mod: p.mod - typ: 'Option_$typ.name' - name: js_dec_name(t) - } - // Already registered? Skip. - if p.table.known_fn(dec_fn.name) { - return - } - // decode_TYPE funcs receive an actual cJSON* object to decode - // cJSON_Parse(str) call is added by the compiler - arg := Var{ - typ: 'cJSON*' - } - dec_fn.args << arg - p.table.register_fn(dec_fn) - // Register encoder fn - mut enc_fn := Fn{ - mod: p.mod - typ: 'cJSON*' - name: js_enc_name(t) - } - // encode_TYPE funcs receive an object to encode - enc_arg := Var{ - typ: t - } - enc_fn.args << enc_arg - p.table.register_fn(enc_fn) - // Code gen decoder - dec += ' -//$t $dec_fn.name (cJSON* root) { -Option ${dec_fn.name}(cJSON* root, $t* res) { -// $t res; - if (!root) { - const char *error_ptr = cJSON_GetErrorPtr(); - if (error_ptr != NULL) { - fprintf(stderr, "Error in decode() for $t error_ptr=: %%s\\n", error_ptr); -// printf("\\nbad js=%%s\\n", js.str); - return v_error(tos2(error_ptr)); - } - } -' - // Code gen encoder - enc += ' -cJSON* $enc_fn.name ($t val) { -cJSON *o = cJSON_CreateObject(); -string res = tos2(""); -' - // Handle arrays - if t.starts_with('array_') { - dec += p.decode_array(t) - enc += p.encode_array(t) - } - // Range through fields - for field in typ.fields { - if field.attr == 'skip' { - continue - } - name := if field.attr.starts_with('json:') { field.attr[5..] } else { field.name } - field_type := p.table.find_type(field.typ) - _typ := field.typ.replace('*', '') - enc_name := js_enc_name(_typ) - if field.attr == 'raw' { - dec += ' res->$field.name = tos2(cJSON_PrintUnformatted(' + 'js_get(root, "$name")));\n' - } - else { - // Now generate decoders for all field types in this struct - // need to do it here so that these functions are generated first - p.gen_json_for_type(field_type) - dec_name := js_dec_name(_typ) - if is_js_prim(_typ) { - dec += ' res->$field.name = $dec_name (js_get(' + 'root, "$name"))' - } - else { - dec += ' $dec_name (js_get(root, "$name"), & (res->$field.name))' - } - dec += ';\n' - } - enc += ' cJSON_AddItemToObject(o, "$name",$enc_name (val.$field.name)); \n' - } - // cJSON_delete - // p.cgen.fns << '$dec return opt_ok(res); \n}' - p.cgen.fns << '$dec return opt_ok(res, sizeof(*res)); \n}' - p.cgen.fns << '/*enc start*/ $enc return o;}' -} - -fn is_js_prim(typ string) bool { - return typ == 'int' || typ == 'string' || typ == 'bool' || typ == 'f32' || typ == 'f64' || typ == 'i8' || typ == 'i16' || typ == 'i64' || typ == 'u16' || typ == 'u32' || typ == 'u64' -} - -fn (p mut Parser) decode_array(array_type string) string { - typ := array_type.replace('array_', '') - t := p.table.find_type(typ) - fn_name := js_dec_name(typ) - // If we have `[]Profile`, have to register a Profile en(de)coder first - p.gen_json_for_type(t) - mut s := '' - if is_js_prim(typ) { - s = '$typ val= $fn_name (jsval); ' - } - else { - s = ' $typ val; $fn_name (jsval, &val); ' - } - return ' -*res = new_array(0, 0, sizeof($typ)); -const cJSON *jsval = NULL; -cJSON_ArrayForEach(jsval, root) -{ -$s - array_push(res, &val); -} -' -} - -fn js_enc_name(typ string) string { - name := 'json__jsencode_$typ' - return name -} - -fn js_dec_name(typ string) string { - name := 'json__jsdecode_$typ' - return name -} - -fn (p &Parser) encode_array(array_type string) string { - typ := array_type.replace('array_', '') - fn_name := js_enc_name(typ) - return ' -o = cJSON_CreateArray(); -for (int i = 0; i < val.len; i++){ - cJSON_AddItemToArray(o, $fn_name ( (($typ*)val.data)[i] )); -} -' -} - diff --git a/vlib/compiler/main.v b/vlib/compiler/main.v deleted file mode 100644 index 1eed6ad3a7..0000000000 --- a/vlib/compiler/main.v +++ /dev/null @@ -1,963 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import ( - os - strings - v.pref - v.builder -) - -pub const ( - Version = '0.1.25' -) - -const ( - supported_platforms = ['windows', 'mac', 'macos', 'linux', 'freebsd', 'openbsd', 'netbsd', - 'dragonfly', 'android', 'js', 'solaris', 'haiku', 'linux_or_macos'] -) - -enum Pass { - // A very short pass that only looks at imports in the beginning of - // each file - imports - // First pass, only parses and saves declarations (fn signatures, - // consts, types). - // Skips function bodies. - // We need this because in V things can be used before they are - // declared. - decl - // Second pass, parses function bodies and generates C or machine code. - main -} - -pub struct V { -pub mut: - mod_file_cacher &builder.ModFileCacher // used during lookup for v.mod to support @VROOT - out_name_c string // name of the temporary C file - files []string // all V files that need to be parsed and compiled - compiled_dir string // contains os.real_path() of the dir of the final file beeing compiled, or the dir itself when doing `v .` - table &Table // table with types, vars, functions etc - cgen &CGen // C code generator - //x64 &x64.Gen - pref &pref.Preferences // all the preferences and settings extracted to a struct for reusability - parsers []Parser // file parsers - vgen_buf strings.Builder // temporary buffer for generated V code (.str() etc) - file_parser_idx map[string]int // map absolute file path to v.parsers index - gen_parser_idx map[string]int - cached_mods []string - module_lookup_paths []string - - v_fmt_all bool // << input set by cmd/tools/vfmt.v - v_fmt_file string // << file given by the user from cmd/tools/vfmt.v - v_fmt_file_result string // >> file with formatted output generated by vlib/compiler/vfmt.v -} - -pub fn new_v(pref &pref.Preferences) &V { - rdir := os.real_path(pref.path) - - mut out_name_c := get_vtmp_filename(pref.out_name, '.tmp.c') - if pref.is_so { - out_name_c = get_vtmp_filename(pref.out_name, '.tmp.so.c') - } - - mut vgen_buf := strings.new_builder(1000) - vgen_buf.writeln('module vgen\nimport strings') - compiled_dir:=if os.is_dir(rdir) { rdir } else { os.dir(rdir) } - - return &V{ - mod_file_cacher: builder.new_mod_file_cacher() - compiled_dir:compiled_dir// if os.is_dir(rdir) { rdir } else { os.dir(rdir) } - table: new_table(pref.obfuscate) - out_name_c: out_name_c - cgen: new_cgen(out_name_c) - //x64: x64.new_gen(out_name) - pref: pref - vgen_buf: vgen_buf - } -} - -// Should be called by main at the end of the compilation process, to cleanup -pub fn (v &V) finalize_compilation() { - // TODO remove - if v.pref.autofree { - /* - println('started freeing v struct') - v.table.typesmap.free() - v.table.obf_ids.free() - v.cgen.lines.free() - free(v.cgen) - for _, f in v.table.fns { - //f.local_vars.free() - f.args.free() - //f.defer_text.free() - } - v.table.fns.free() - free(v.table) - //for p in parsers {} - println('done!') - */ - } -} - -pub fn (v mut V) add_parser(parser Parser) int { - pidx := v.parsers.len - v.parsers << parser - file_path := if os.is_abs_path(parser.file_path) { parser.file_path } else { os.real_path(parser.file_path) } - v.file_parser_idx[file_path] = pidx - return pidx -} - -pub fn (v &V) get_file_parser_index(file string) ?int { - file_path := if os.is_abs_path(file) { file } else { os.real_path(file) } - if file_path in v.file_parser_idx { - return v.file_parser_idx[file_path] - } - return error('parser for "$file" not found') -} - -// find existing parser or create new one. returns v.parsers index -pub fn (v mut V) parse(file string, pass Pass) int { - // println('parse($file, $pass)') - pidx := v.get_file_parser_index(file) or { - mut p := v.new_parser_from_file(file) - p.parse(pass) - // if p.pref.autofree { p.scanner.text.free() free(p.scanner) } - return v.add_parser(p) - } - // println('matched ' + v.parsers[pidx].file_path + ' with $file') - v.parsers[pidx].parse(pass) - // if v.parsers[i].pref.autofree { v.parsers[i].scanner.text.free() free(v.parsers[i].scanner) } - return pidx -} - -pub fn (v mut V) compile() { - //println('compile()') - // Emily: Stop people on linux from being able to build with msvc - if os.user_os() != 'windows' && v.pref.ccompiler == 'msvc' { - verror('Cannot build with msvc on ${os.user_os()}') - } - mut cgen := v.cgen - cgen.genln('// Generated by V') - if v.pref.verbosity.is_higher_or_equal(.level_three) { - println('all .v files before:') - println(v.files) - } - v.add_v_files_to_compile() - if v.pref.verbosity.is_higher_or_equal(.level_three) { - println('all .v files:') - println(v.files) - } - /* - if v.pref.is_debug { - println('\nparsers:') - for q in v.parsers { - println(q.file_name) - } - println('\nfiles:') - for q in v.files { - println(q) - } - } - */ - - // First pass (declarations) - for file in v.files { - v.parse(file, .decl) - } - // Main pass - cgen.pass = .main - if v.pref.is_debug { - $if js { - cgen.genln('const VDEBUG = 1;\n') - } $else { - cgen.genln('#define VDEBUG (1)') - } - } - if v.pref.prealloc { - cgen.genln('#define VPREALLOC (1)') - } - if v.pref.os == .js { - cgen.genln('#define _VJS (1) ') - } - v_hash := vhash() - $if js { - cgen.genln('const V_COMMIT_HASH = "$v_hash";\n') - } $else { - cgen.genln('#ifndef V_COMMIT_HASH') - cgen.genln('#define V_COMMIT_HASH "$v_hash"') - cgen.genln('#endif') - } - q := cgen.nogen // TODO hack - cgen.nogen = false - $if js { - cgen.genln(js_headers) - } $else { - if !v.pref.is_bare { - cgen.genln('#include ') // int64_t etc - } - else { - cgen.genln('#include ') - } - - if v.pref.compile_defines_all.len > 0 { - cgen.genln('') - cgen.genln('// All custom defines : ' + v.pref.compile_defines_all.join(',')) - cgen.genln('// Turned ON custom defines: ' + v.pref.compile_defines.join(',')) - for cdefine in v.pref.compile_defines { - cgen.genln('#define CUSTOM_DEFINE_${cdefine}') - } - cgen.genln('//') - cgen.genln('') - } - - cgen.genln(c_builtin_types) - - if !v.pref.is_bare { - cgen.genln(c_headers) - } - else { - cgen.genln(bare_c_headers) - } - } - v.generate_hotcode_reloading_declarations() - // We need the cjson header for all the json decoding that will be done in - // default mode - imports_json := 'json' in v.table.imports - if v.pref.build_mode == .default_mode { - if imports_json { - cgen.genln('#include "cJSON.h"') - } - } - if v.pref.build_mode == .default_mode { - // If we declare these for all modes, then when running `v a.v` we'll get - // `/usr/bin/ld: multiple definition of 'total_m'` - $if !js { - cgen.genln('int g_test_oks = 0;') - cgen.genln('int g_test_fails = 0;') - } - if imports_json { - cgen.genln(' -#define js_get(object, key) cJSON_GetObjectItemCaseSensitive((object), (key)) -') - } - } - if '-debug_alloc' in os.args { - cgen.genln('#define DEBUG_ALLOC 1') - } - if v.pref.is_live && v.pref.os != .windows { - cgen.includes << '#include ' - } - // cgen.genln('/*================================== FNS =================================*/') - cgen.genln('// this line will be replaced with definitions') - mut defs_pos := cgen.lines.len - 1 - if defs_pos == -1 { - defs_pos = 0 - } - cgen.nogen = q - for i, file in v.files { - v.parse(file, .main) - // if p.pref.autofree { p.scanner.text.free() free(p.scanner) } - // Format all files (don't format automatically generated vlib headers) - // if !v.pref.nofmt && !file.contains('/vlib/') { - // new vfmt is not ready yet - // } - } - // add parser generated V code (str() methods etc) - mut vgen_parser := v.new_parser_from_string(v.vgen_buf.str()) - // free the string builder which held the generated methods - v.vgen_buf.free() - vgen_parser.is_vgen = true - // v.add_parser(vgen_parser) - vgen_parser.parse(.main) - // Generate .vh if we are building a module - if v.pref.build_mode == .build_module { - //generate_vh(v.pref.path) - } - // All definitions - mut def := strings.new_builder(10000) // Avoid unnecessary allocations - def.writeln(cgen.const_defines.join_lines()) - $if !js { - def.writeln(cgen.includes.join_lines()) - def.writeln(cgen.typedefs.join_lines()) - def.writeln(v.type_definitions()) - if !v.pref.is_bare { - def.writeln('\nstring _STR(const char*, ...);\n') - def.writeln('\nstring _STR_TMP(const char*, ...);\n') - } - def.writeln(cgen.fns.join_lines()) // fn definitions - def.writeln(v.interface_table()) - } $else { - def.writeln(v.type_definitions()) - } - def.writeln(cgen.consts.join_lines()) - def.writeln(cgen.thread_args.join_lines()) - if v.pref.is_prof { - def.writeln('; // Prof counters:') - def.writeln(v.prof_counters()) - } - cgen.lines[defs_pos] = def.str() - v.generate_init() - v.generate_main() - v.generate_hot_reload_code() - if v.pref.verbosity.is_higher_or_equal(.level_three) { - v.log('flags=') - for flag in v.get_os_cflags() { - println(' * ' + flag.format()) - } - } - $if js { - cgen.genln('main__main();') - } - cgen.save() - v.cc() - //println(v.table.imports) - //println(v.table.modules) -} - -pub fn (v mut V) compile2() { - if os.user_os() != 'windows' && v.pref.ccompiler == 'msvc' { - verror('Cannot build with msvc on ${os.user_os()}') - } - //cgen.genln('// Generated by V') - //println('compile2()') - if v.pref.verbosity.is_higher_or_equal(.level_three) { - println('all .v files before:') - println(v.files) - } - // v1 compiler files - //v.add_v_files_to_compile() - //v.files << v.dir - // v2 compiler - v.files << v.get_builtin_files() - v.files << v.get_user_files() - v.set_module_lookup_paths() - if v.pref.verbosity.is_higher_or_equal(.level_three) { - println('all .v files:') - println(v.files) - } - mut b := v.new_v2() - b.build_c(v.files, v.out_name_c)// v.pref.out_name + '.c') - v.cc() -} - -pub fn (v mut V) compile_x64() { - $if !linux { - println('v -x64 can only generate Linux binaries for now') - println('You are not on a Linux system, so you will not ' + 'be able to run the resulting executable') - } - //v.files << v.v_files_from_dir(os.join_path(v.pref.vlib_path,'builtin','bare')) - v.files << v.pref.path - v.set_module_lookup_paths() - mut b := v.new_v2() - // move all this logic to v2 - b.build_x64(v.files, v.pref.out_name) -} - -// make v2 from v1 -fn (v &V) new_v2() builder.Builder { - mut b := builder.new_builder(v.pref) - b = { b| - os: v.pref.os, - module_path: v_modules_path, - compiled_dir: v.compiled_dir, - module_search_paths: v.module_lookup_paths - } - return b -} - -fn (v mut V) generate_init() { - $if js { - return - } - if v.pref.build_mode == .build_module { - nogen := v.cgen.nogen - v.cgen.nogen = false - consts_init_body := v.cgen.consts_init.join_lines() - init_fn_name := mod_gen_name(v.pref.mod) + '__init_consts' - v.cgen.genln('void ${init_fn_name}();\nvoid ${init_fn_name}() {\n$consts_init_body\n}') - v.cgen.nogen = nogen - } - if v.pref.build_mode == .default_mode { - mut call_mod_init := '' - mut call_mod_init_consts := '' - if 'builtin' in v.cached_mods { - v.cgen.genln('void builtin__init_consts();') - call_mod_init_consts += 'builtin__init_consts();\n' - } - for mod in v.table.imports { - init_fn_name := mod_gen_name(mod) + '__init' - if v.table.known_fn(init_fn_name) { - call_mod_init += '${init_fn_name}();\n' - } - if mod in v.cached_mods { - v.cgen.genln('void ${init_fn_name}_consts();') - call_mod_init_consts += '${init_fn_name}_consts();\n' - } - } - consts_init_body := v.cgen.consts_init.join_lines() - if v.pref.is_bare { - // vlib can't have init_consts() - v.cgen.genln(' - void init() { - $call_mod_init_consts - $consts_init_body - builtin__init(); - $call_mod_init - } - ') - } - if !v.pref.is_bare && !v.pref.is_so { - // vlib can't have `init_consts()` - v.cgen.genln('void init() { -#if VPREALLOC -g_m2_buf = malloc(50 * 1000 * 1000); -g_m2_ptr = g_m2_buf; -puts("allocated 50 mb"); -#endif -$call_mod_init_consts -$consts_init_body -builtin__init(); -$call_mod_init -}') - } - if !v.pref.is_bare { - v.generate_str_definitions() - } - } -} - -pub fn (v mut V) generate_main() { - mut cgen := v.cgen - $if js { - return - } - if v.pref.is_vlines { - // After this point, the v files are compiled. - // The rest is auto generated code, which will not have - // different .v source file/line numbers. - lines_so_far := cgen.lines.join('\n').count('\n') + 5 - cgen.genln('') - cgen.genln('// Reset the file/line numbers') - cgen.lines << '#line $lines_so_far "${cescaped_path(os.real_path(cgen.out_path))}"' - cgen.genln('') - } - // Make sure the main function exists - // Obviously we don't need it in libraries - if v.pref.build_mode != .build_module { - if !v.table.main_exists() && !v.pref.is_test { - // It can be skipped in single file programs - // But make sure that there's some code outside of main() - if (v.pref.is_script && cgen.fn_main.trim_space() != '') || v.pref.is_repl { - // println('Generating main()...') - v.gen_main_start(true) - cgen.genln('$cgen.fn_main;') - v.gen_main_end('return 0') - } - else if v.v_fmt_file=='' && !v.pref.is_repl && !v.pref.is_so { - verror('function `main` is not declared in the main module\nPlease add: \nfn main(){\n}\n... to your main program .v file, and try again.') - } - } - else if v.pref.is_test { - if v.table.main_exists() { - verror('test files cannot have function `main`') - } - test_fn_names := v.table.all_test_function_names() - if test_fn_names.len == 0 { - verror('test files need to have at least one test function') - } - // Generate a C `main`, which calls every single test function - v.gen_main_start(false) - if v.pref.is_stats { - cgen.genln('BenchedTests bt = main__start_testing(${test_fn_names.len},tos3("$v.pref.path"));') - } - for tfname in test_fn_names { - if v.pref.is_stats { - cgen.genln('BenchedTests_testing_step_start(&bt, tos3("$tfname"));') - } - cgen.genln('${tfname}();') - if v.pref.is_stats { - cgen.genln('BenchedTests_testing_step_end(&bt);') - } - } - if v.pref.is_stats { - cgen.genln('BenchedTests_end_testing(&bt);') - } - v.gen_main_end('return g_test_fails > 0') - } - else if v.table.main_exists() && !v.pref.is_so { - v.gen_main_start(true) - cgen.genln(' main__main();') - if !v.pref.is_bare { - cgen.genln('#if VPREALLOC') - cgen.genln('free(g_m2_buf);') - cgen.genln('puts("freed mem buf");') - cgen.genln('#endif') - } - v.gen_main_end('return 0') - } - } -} - -pub fn (v mut V) gen_main_start(add_os_args bool) { - if v.pref.os == .windows { - if 'glfw' in v.table.imports { - // GUI application - v.cgen.genln('int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, LPWSTR cmd_line, int show_cmd) { ') - v.cgen.genln(' typedef LPWSTR*(WINAPI *cmd_line_to_argv)(LPCWSTR, int*);') - v.cgen.genln(' HMODULE shell32_module = LoadLibrary(L"shell32.dll");') - v.cgen.genln(' cmd_line_to_argv CommandLineToArgvW = (cmd_line_to_argv)GetProcAddress(shell32_module, "CommandLineToArgvW");') - v.cgen.genln(' int argc;') - v.cgen.genln(' wchar_t** argv = CommandLineToArgvW(cmd_line, &argc);') - } else { - // Console application - v.cgen.genln('int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) { ') - } - } else { - v.cgen.genln('int main(int argc, char** argv) { ') - } - v.cgen.genln(' init();') - if add_os_args && 'os' in v.table.imports { - if v.pref.os == .windows { - v.cgen.genln(' os__args = os__init_os_args_wide(argc, argv);') - } else { - v.cgen.genln(' os__args = os__init_os_args(argc, (byteptr*)argv);') - } - } - v.generate_hotcode_reloading_main_caller() - v.cgen.genln('') -} - -pub fn (v mut V) gen_main_end(return_statement string) { - v.cgen.genln('') - v.cgen.genln(' $return_statement;') - v.cgen.genln('}') -} - -pub fn (v &V) v_files_from_dir(dir string) []string { - mut res := []string - if !os.exists(dir) { - if dir == 'compiler' && os.is_dir('vlib') { - println('looks like you are trying to build V with an old command') - println('use `v -o v cmd/v` instead of `v -o v compiler`') - } - verror("$dir doesn't exist") - } - else if !os.is_dir(dir) { - verror("$dir isn't a directory!") - } - mut files := os.ls(dir)or{ - panic(err) - } - if v.pref.verbosity.is_higher_or_equal(.level_three) { - println('v_files_from_dir ("$dir")') - } - files.sort() - for file in files { - if !file.ends_with('.v') && !file.ends_with('.vh') { - continue - } - if file.ends_with('_test.v') { - continue - } - if (file.ends_with('_win.v') || file.ends_with('_windows.v')) && v.pref.os != .windows { - continue - } - if (file.ends_with('_lin.v') || file.ends_with('_linux.v')) && v.pref.os != .linux { - continue - } - if (file.ends_with('_mac.v') || file.ends_with('_darwin.v')) && v.pref.os != .mac { - continue - } - if file.ends_with('_nix.v') && v.pref.os == .windows { - continue - } - if file.ends_with('_android.v') && v.pref.os != .android { - continue - } - if file.ends_with('_freebsd.v') && v.pref.os != .freebsd { - continue - } - if file.ends_with('_solaris.v') && v.pref.os != .solaris { - continue - } - if file.ends_with('_js.v') && v.pref.os != .js { - continue - } - if file.ends_with('_c.v') && v.pref.os == .js { - continue - } - if v.pref.compile_defines_all.len > 0 && file.contains('_d_') { - mut allowed := false - for cdefine in v.pref.compile_defines { - file_postfix := '_d_${cdefine}.v' - if file.ends_with(file_postfix) { - allowed = true - break - } - } - if !allowed { - continue - } - } - res << os.join_path(dir, file) - } - return res -} - -// Parses imports, adds necessary libs, and then user files -pub fn (v mut V) add_v_files_to_compile() { - v.set_module_lookup_paths() - mut builtin_files := v.get_builtin_files() - if v.pref.is_bare { - // builtin_files = [] - } - // Builtin cache exists? Use it. - if v.pref.is_cache { - builtin_vh := os.join_path(v_modules_path, 'vlib', 'builtin.vh') - if os.exists(builtin_vh) { - v.cached_mods << 'builtin' - builtin_files = [builtin_vh] - } - } - if v.pref.verbosity.is_higher_or_equal(.level_two) { - v.log('v.add_v_files_to_compile > builtin_files: $builtin_files') - } - // Parse builtin imports - for file in builtin_files { - // add builtins first - v.files << file - mut p := v.new_parser_from_file(file) - p.parse(.imports) - // if p.pref.autofree { p.scanner.text.free() free(p.scanner) } - v.add_parser(p) - } - // Parse user imports - for file in v.get_user_files() { - mut p := v.new_parser_from_file(file) - p.parse(.imports) - if p.v_script { - v.log('imports0:') - println(v.table.imports) - println(v.files) - p.register_import('os', 0) - p.table.imports << 'os' - p.table.register_module('os') - } - // if p.pref.autofree { p.scanner.text.free() free(p.scanner) } - v.add_parser(p) - } - // Parse lib imports - v.parse_lib_imports() - if v.pref.verbosity.is_higher_or_equal(.level_three) { - v.log('imports:') - println(v.table.imports) - } - // resolve deps and add imports in correct order - imported_mods := v.resolve_deps().imports() - for mod in imported_mods { - if mod == 'builtin' || mod == 'main' { - // builtin already added - // main files will get added last - continue - } - // use cached built module if exists - // Cached modules are broken currently - /* - if v.pref.vpath != '' && v.pref.build_mode != .build_module && !mod.contains('vweb') { - mod_path := mod.replace('.', os.path_separator) - vh_path := '$v_modules_path${os.path_separator}vlib${os.path_separator}${mod_path}.vh' - if v.pref.is_cache && os.exists(vh_path) { - eprintln('using cached module `$mod`: $vh_path') - v.cached_mods << mod - v.files << vh_path - continue - } - } - */ - // standard module - vfiles := v.get_imported_module_files(mod) - for file in vfiles { - v.files << file - } - } - // add remaining main files last - for p in v.parsers { - if p.mod != 'main' { - continue - } - if p.is_vgen { - continue - } - v.files << p.file_path - } -} - -pub fn (v &V) get_builtin_files() []string { - // Lookup for built-in folder in lookup path. - // Assumption: `builtin/` folder implies usable implementation of builtin - for location in v.pref.lookup_path { - if !os.exists(os.join_path(location, 'builtin')) { - continue - } - if v.pref.is_bare { - return v.v_files_from_dir(os.join_path(location, 'builtin', 'bare')) - } - $if js { - return v.v_files_from_dir(os.join_path(location, 'builtin', 'js')) - } - return v.v_files_from_dir(os.join_path(location, 'builtin')) - } - // Panic. We couldn't find the folder. - verror('`builtin/` not included on module lookup path. -Did you forget to add vlib to the path? (Use @vlib for default vlib)') - panic('Unreachable code reached.') -} - -// get user files -pub fn (v &V) get_user_files() []string { - mut dir := v.pref.path - v.log('get_v_files($dir)') - // Need to store user files separately, because they have to be added after - // libs, but we dont know which libs need to be added yet - mut user_files := []string - - // See cmd/tools/preludes/README.md for more info about what preludes are - vroot := os.dir(pref.vexe_path()) - preludes_path := os.join_path(vroot, 'cmd', 'tools', 'preludes') - if v.pref.is_live { - user_files << os.join_path(preludes_path, 'live_main.v') - } - if v.pref.is_solive { - user_files << os.join_path(preludes_path, 'live_shared.v') - } - if v.pref.is_test { - user_files << os.join_path(preludes_path, 'tests_assertions.v') - } - if v.pref.is_test && v.pref.is_stats { - user_files << os.join_path(preludes_path, 'tests_with_stats.v') - } - - is_test := dir.ends_with('_test.v') - mut is_internal_module_test := false - if is_test { - tcontent := os.read_file(dir)or{ - panic('$dir does not exist') - } - slines := tcontent.trim_space().split_into_lines() - for sline in slines { - line := sline.trim_space() - if line.len > 2 { - if line[0] == `/` && line[1] == `/` { - continue - } - if line.starts_with('module ') && !line.starts_with('module main') { - is_internal_module_test = true - break - } - } - } - } - if is_internal_module_test { - // v volt/slack_test.v: compile all .v files to get the environment - single_test_v_file := os.real_path(dir) - if v.pref.verbosity.is_higher_or_equal(.level_two) { - v.log('> Compiling an internal module _test.v file $single_test_v_file .') - v.log('> That brings in all other ordinary .v files in the same module too .') - } - user_files << single_test_v_file - dir = os.base_dir(single_test_v_file) - } - is_real_file := os.exists(dir) && !os.is_dir(dir) - if is_real_file && ( dir.ends_with('.v') || dir.ends_with('.vsh') ) { - single_v_file := dir - // Just compile one file and get parent dir - user_files << single_v_file - if v.pref.verbosity.is_higher_or_equal(.level_two) { - v.log('> just compile one file: "${single_v_file}"') - } - } - else { - if v.pref.verbosity.is_higher_or_equal(.level_two) { - v.log('> add all .v files from directory "${dir}" ...') - } - // Add .v files from the directory being compiled - files := v.v_files_from_dir(dir) - for file in files { - user_files << file - } - } - if user_files.len == 0 { - println('No input .v files') - exit(1) - } - if v.pref.verbosity.is_higher_or_equal(.level_two) { - v.log('user_files: $user_files') - } - return user_files -} - -// get module files from already parsed imports -fn (v &V) get_imported_module_files(mod string) []string { - mut files := []string - for p in v.parsers { - if p.mod == mod { - files << p.file_path - } - } - return files -} - -// parse deps from already parsed builtin/user files -pub fn (v mut V) parse_lib_imports() { - mut done_imports := []string - for i in 0 .. v.parsers.len { - for _, mod in v.parsers[i].import_table.imports { - if mod in done_imports { - continue - } - import_path := v.parsers[i].find_module_path(mod) or { - v.parsers[i].error_with_token_index('cannot import module "$mod" (not found)\n$err', v.parsers[i].import_table.get_import_tok_idx(mod)) - break - } - vfiles := v.v_files_from_dir(import_path) - if vfiles.len == 0 { - v.parsers[i].error_with_token_index('cannot import module "$mod" (no .v files in "$import_path")', v.parsers[i].import_table.get_import_tok_idx(mod)) - } - // Add all imports referenced by these libs - for file in vfiles { - pidx := v.parse(file, .imports) - p_mod := v.parsers[pidx].mod - if p_mod != mod { - v.parsers[pidx].error_with_token_index('bad module definition: ${v.parsers[pidx].file_path} imports module "$mod" but $file is defined as module `$p_mod`', 0) - } - } - done_imports << mod - } - } -} - - -pub fn (v &V) log(s string) { - if !v.pref.verbosity.is_higher_or_equal(.level_two) { - return - } - println(s) -} - -pub fn verror(s string) { - println('V error: $s') - os.flush() - exit(1) -} - -pub fn vhash() string { - mut buf := [50]byte - buf[0] = 0 - C.snprintf(charptr(buf), 50, '%s', C.V_COMMIT_HASH) - return tos_clone(buf) -} - -pub fn cescaped_path(s string) string { - return s.replace('\\', '\\\\') -} - -pub fn os_from_string(os string) pref.OS { - match os { - 'linux' { - return .linux - } - 'windows' { - return .windows - } - 'mac' { - return .mac - } - 'macos' { - return .mac - } - 'freebsd' { - return .freebsd - } - 'openbsd' { - return .openbsd - } - 'netbsd' { - return .netbsd - } - 'dragonfly' { - return .dragonfly - } - 'js' { - return .js - } - 'solaris' { - return .solaris - } - 'android' { - return .android - } - 'msvc' { - // notice that `-os msvc` became `-cc msvc` - verror('use the flag `-cc msvc` to build using msvc') - } - 'haiku' { - return .haiku - } - 'linux_or_macos' { - return .linux - } - else { - panic('bad os $os') - }} - // println('bad os $os') // todo panic? - return .linux -} - -// -pub fn set_vroot_folder(vroot_path string) { - // Preparation for the compiler module: - // VEXE env variable is needed so that compiler.vexe_path() - // can return it later to whoever needs it: - vname := if os.user_os() == 'windows' { 'v.exe' } else { 'v' } - os.setenv('VEXE', os.real_path([vroot_path, vname].join(os.path_separator)), true) -} - -pub fn (v mut V) generate_str_definitions() { - // _STR function can't be defined in vlib - v.cgen.genln(' -string _STR(const char *fmt, ...) { - va_list argptr; - va_start(argptr, fmt); - size_t len = vsnprintf(0, 0, fmt, argptr) + 1; - va_end(argptr); - byte* buf = malloc(len); - va_start(argptr, fmt); - vsprintf((char *)buf, fmt, argptr); - va_end(argptr); -#ifdef DEBUG_ALLOC - puts("_STR:"); - puts(buf); -#endif - return tos2(buf); -} - -string _STR_TMP(const char *fmt, ...) { - va_list argptr; - va_start(argptr, fmt); - //size_t len = vsnprintf(0, 0, fmt, argptr) + 1; - va_end(argptr); - va_start(argptr, fmt); - vsprintf((char *)g_str_buf, fmt, argptr); - va_end(argptr); -#ifdef DEBUG_ALLOC - //puts("_STR_TMP:"); - //puts(g_str_buf); -#endif - return tos2(g_str_buf); -} - -') -} diff --git a/vlib/compiler/modules.v b/vlib/compiler/modules.v deleted file mode 100644 index 92cd22ae9a..0000000000 --- a/vlib/compiler/modules.v +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import ( - os - v.pref -) - -pub const ( - v_modules_path = pref.default_module_path -) -// Holds import information scoped to the parsed file -struct ImportTable { -mut: - imports map[string]string // alias => module - used_imports []string // alias - import_tok_idx map[string]int // module => idx -} -// 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 { - for m in table.imports { - if m.contains('.') && m.contains(mod) { - m_parts := m.split('.') - m_path := m_parts.join(os.path_separator) - if mod == m_parts[m_parts.len - 1] && file_path.contains(m_path) { - return m - } - } - } - return mod -} - -fn new_import_table() ImportTable { - return ImportTable{ - imports: map[string]string - } -} - -fn (p mut Parser) register_import(mod string, tok_idx int) { - p.register_import_alias(mod, mod, tok_idx) -} - -fn (p mut Parser) register_import_alias(alias string, mod string, tok_idx int) { - // NOTE: come back here - // if alias in it.imports && it.imports[alias] == mod {} - if alias in p.import_table.imports && p.import_table.imports[alias] != mod { - p.error('cannot import $mod as $alias: import name $alias already in use') - } - if mod.contains('.internal.') && !p.is_vgen { - mod_parts := mod.split('.') - mut internal_mod_parts := []string - for part in mod_parts { - if part == 'internal' { - break - } - internal_mod_parts << part - } - internal_parent := internal_mod_parts.join('.') - if !p.mod.starts_with(internal_parent) { - p.error('module $mod can only be imported internally by libs') - } - } - p.import_table.imports[alias] = mod - p.import_table.import_tok_idx[mod] = tok_idx -} - -fn (it &ImportTable) get_import_tok_idx(mod string) int { - return it.import_tok_idx[mod] -} - -fn (it &ImportTable) known_import(mod string) bool { - return mod in it.imports || it.is_aliased(mod) -} - -fn (it &ImportTable) known_alias(alias string) bool { - return alias in it.imports -} - -fn (it &ImportTable) is_aliased(mod string) bool { - for _, val in it.imports { - if val == mod { - return true - } - } - return false -} - -fn (it &ImportTable) resolve_alias(alias string) string { - return it.imports[alias] -} - -fn (it mut ImportTable) register_used_import(alias string) { - if !(alias in it.used_imports) { - it.used_imports << alias - } -} - -fn (it &ImportTable) is_used_import(alias string) bool { - return alias in it.used_imports -} - -// should module be accessable -pub fn (p &Parser) is_mod_in_scope(mod string) bool { - mut mods_in_scope := ['', 'builtin', 'main', p.mod] - for _, m in p.import_table.imports { - mods_in_scope << m - } - return mod in mods_in_scope -} - -// return resolved dep graph (order deps) -pub fn (v &V) resolve_deps() &DepGraph { - graph := v.import_graph() - deps_resolved := graph.resolve() - if !deps_resolved.acyclic { - verror('import cycle detected between the following modules: \n' + deps_resolved.display_cycles()) - } - return deps_resolved -} - -// graph of all imported modules -pub fn (v &V) import_graph() &DepGraph { - mut graph := new_dep_graph() - for p in v.parsers { - mut deps := []string - for _, m in p.import_table.imports { - deps << m - } - graph.add(p.mod, deps) - } - return graph -} - -// get ordered imports (module speficic dag method) -pub fn (graph &DepGraph) imports() []string { - mut mods := []string - for node in graph.nodes { - mods << node.name - } - return mods -} - -[inline] -fn (v &V) module_path(mod string) string { - // submodule support - return mod.replace('.', os.path_separator) -} - -// 'strings' => 'VROOT/vlib/strings' -// 'installed_mod' => '~/.vmodules/installed_mod' -// 'local_mod' => '/path/to/current/dir/local_mod' -fn (v mut V) set_module_lookup_paths() { - // Module search order: - // 0) V test files are very commonly located right inside the folder of the - // module, which they test. Adding the parent folder of the module folder - // with the _test.v files, *guarantees* that the tested module can be found - // without needing to set custom options/flags. - // 1) search in the *same* directory, as the compiled final v program source - // (i.e. the . in `v .` or file.v in `v file.v`) - // 2) search in the modules/ in the same directory. - // 3) search in the provided paths - // By default, these are what (3) contains: - // 3.1) search in vlib/ - // 3.2) search in ~/.vmodules/ (i.e. modules installed with vpm) - v.module_lookup_paths = [] - if v.pref.is_test { - v.module_lookup_paths << os.base_dir(v.compiled_dir) // pdir of _test.v - } - v.module_lookup_paths << v.compiled_dir - x := os.join_path(v.compiled_dir, 'modules') - if v.pref.verbosity.is_higher_or_equal(.level_two) { - println('x: "$x"') - } - v.module_lookup_paths << os.join_path(v.compiled_dir, 'modules') - v.module_lookup_paths << v.pref.lookup_path - if v.pref.verbosity.is_higher_or_equal(.level_two) { - v.log('v.module_lookup_paths') //: $v.module_lookup_paths') - println(v.module_lookup_paths) - } -} - -fn (p mut Parser) find_module_path(mod string) ?string { - vmod_file_location := p.v.mod_file_cacher.get( p.file_path_dir ) - mut module_lookup_paths := []string - if vmod_file_location.vmod_file.len != 0 { - if ! vmod_file_location.vmod_folder in p.v.module_lookup_paths { - module_lookup_paths << vmod_file_location.vmod_folder - } - } - module_lookup_paths << p.v.module_lookup_paths - - mod_path := p.v.module_path(mod) - for lookup_path in module_lookup_paths { - try_path := os.join_path(lookup_path, mod_path) - if p.v.pref.verbosity.is_higher_or_equal(.level_three) { - println(' >> trying to find $mod in $try_path ...') - } - if os.is_dir(try_path) { - if p.v.pref.verbosity.is_higher_or_equal(.level_three) { - println(' << found $try_path .') - } - return try_path - } - } - return error('module "$mod" not found in ${module_lookup_paths}') -} - -[inline] -fn mod_gen_name(mod string) string { - return mod.replace('.', '_dot_') -} - -[inline] -fn mod_gen_name_rev(mod string) string { - return mod.replace('_dot_', '.') -} diff --git a/vlib/compiler/optimization.v b/vlib/compiler/optimization.v deleted file mode 100644 index 2c144c9790..0000000000 --- a/vlib/compiler/optimization.v +++ /dev/null @@ -1,48 +0,0 @@ -module compiler -// `a in [1,2,3]` => `a == 1 || a == 2 || a == 3` -// avoid allocation -// `typ` is the type of `a` -// `ph` is for string_eq() -fn (p mut Parser) in_optimization(typ string, ph int) { - p.check(.lsbr) - if p.tok == .rsbr { - p.error('`x in []` is always false') - } - mut i := 0 - // Get `a` expr value (can be a string literal, not a variable) - expr := p.cgen.cur_line[ph..] - is_str := typ == 'string' - // println('!! $p.expr_var.name => $name ($typ)') - for p.tok != .rsbr && p.tok != .eof { - if i > 0 { - if is_str { - p.gen(' || string_eq($expr, ') - } - else { - p.gen(' || $expr == ') - } - } - if i == 0 { - if is_str { - p.cgen.set_placeholder(ph, ' (string_eq(') - p.gen(', ') - } - else { - p.cgen.set_placeholder(ph, ' (') - p.gen(' ==') - } - } - p.check_types(p.bool_expression(), typ) - if is_str { - p.gen(')') - } - if p.tok != .rsbr { - p.check(.comma) - p.fspace() - } - i++ - } - p.gen(')') - p.check(.rsbr) -} - diff --git a/vlib/compiler/query.v b/vlib/compiler/query.v deleted file mode 100644 index ceec14fa26..0000000000 --- a/vlib/compiler/query.v +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import strings - -fn sql_params2params_gen(sql_params []string, sql_types []string, qprefix string) string { - mut params_gen := '' - for i, mparam in sql_params { - param := mparam.trim_space() - paramtype := sql_types[i] - if param[0].is_digit() { - params_gen += '${qprefix}params[$i] = int_str($param).str;\n' - } - else if param[0] == `\'` { - sparam := param.trim("\'") - params_gen += '${qprefix}params[$i] = "$sparam";\n' - } - else { - // A variable like q.nr_orders - if paramtype == 'int' { - params_gen += '${qprefix}params[$i] = int_str( $param ).str;\n' - } - else if paramtype == 'string' { - params_gen += '${qprefix}params[$i] = ${param}.str;\n' - } - else { - verror('orm: only int and string variable types are supported in queries') - } - } - } - // println('>>>>>>>> params_gen') - // println( params_gen ) - return params_gen -} - -// `db.select from User where id == 1 && nr_bookings > 0` -fn (p mut Parser) select_query(fn_ph int) string { - // NB: qprefix and { p.sql_i, p.sql_params, p.sql_types } SHOULD be reset for each query, - // because we can have many queries in the _same_ scope. - qprefix := p.get_tmp().replace('tmp', 'sql') + '_' - p.sql_i = 0 - p.sql_params = [] - if false { - } - p.sql_types = [] - mut q := 'select ' - p.check(.key_select) - p.fspace() - n := p.check_name() - p.fspace() - if n == 'count' { - q += 'count(*) from ' - p.check_name() - p.fspace() - } - table_name := p.check_name() - p.fspace() - // Register this type's fields as variables so they can be used in `where` - // expressions - typ := p.table.find_type(table_name) - if typ.name == '' { - p.error('unknown type `$table_name`') - } - // fields := typ.fields.filter(typ == 'string' || typ == 'int') - // get only string and int fields - mut fields := []Var - for i, field in typ.fields { - if !(field.typ in ['string', 'int', 'bool']) { - println('orm: skipping $field.name') - continue - } - if field.attr.contains('skip') { - continue - } - fields << field - } - if fields.len == 0 { - p.error('V orm: select: empty fields in `$table_name`') - } - if fields[0].name != 'id' { - p.error('V orm: `id int` must be the first field in `$table_name`') - } - // 'select id, name, age from...' - if n == 'from' { - for i, field in fields { - q += field.name - if i < fields.len - 1 { - q += ', ' - } - } - q += ' from ' - } - for field in fields { - // println('registering sql field var $field.name') - if !(field.typ in ['string', 'int', 'bool']) { - println('orm: skipping $field.name') - continue - } - p.register_var({ - field | - is_mut:true, - is_used:true, - is_changed:true - }) - } - q += table_name + 's' - // `where` statement - if p.tok == .name && p.lit == 'where' { - p.next() - p.fspace() - p.is_sql = true - _,expr := p.tmp_expr() - p.is_sql = false - q += ' where ' + expr - } - // limit? - mut query_one := false - if p.tok == .name && p.lit == 'limit' { - p.fspace() - p.next() - p.fspace() - p.is_sql = true - _,limit := p.tmp_expr() - p.fspace() - p.is_sql = false - q += ' limit ' + limit - // `limit 1` means we are getting `?User`, not `[]User` - if limit.trim_space() == '1' { - query_one = true - } - } - println('sql query="$q"') - p.cgen.insert_before('// DEBUG_SQL prefix: $qprefix | fn_ph: $fn_ph | query: "$q" ') - if n == 'count' { - p.cgen.set_placeholder(fn_ph, 'pg__DB_q_int(') - p.gen(', tos2("$q"))') - } - else { - // Build an object, assign each field. - tmp := p.get_tmp() - mut obj_gen := strings.new_builder(300) - for i, field in fields { - mut cast := '' - if field.typ == 'int' { - cast = 'v_string_int' - } - else if field.typ == 'bool' { - cast = 'string_bool' - } - obj_gen.writeln('${qprefix}${tmp}.$field.name = ' + '${cast}(*(string*)array_get(${qprefix}row.vals, $i));') - } - // One object - if query_one { - mut params_gen := sql_params2params_gen(p.sql_params, p.sql_types, qprefix) - p.cgen.insert_before(' - -char* ${qprefix}params[$p.sql_i]; -$params_gen - -Option_${table_name} opt_${qprefix}$tmp; -void* ${qprefix}res = PQexecParams(db.conn, "$q", $p.sql_i, 0, ${qprefix}params, 0, 0, 0) ; -array_pg__Row ${qprefix}rows = pg__res_to_rows ( ${qprefix}res ) ; -Option_pg__Row opt_${qprefix}row = pg__rows_first_or_empty( ${qprefix}rows ); -if (! opt_${qprefix}row . ok ) { - opt_${qprefix}$tmp = v_error( opt_${qprefix}row . error ); -}else{ - $table_name ${qprefix}$tmp; - pg__Row ${qprefix}row = *(pg__Row*) opt_${qprefix}row . data; -${obj_gen.str()} - opt_${qprefix}$tmp = opt_ok( & ${qprefix}$tmp, sizeof($table_name) ); -} - -') - p.cgen.resetln('opt_${qprefix}$tmp') - } - // Array - else { - q += ' order by id' - params_gen := sql_params2params_gen(p.sql_params, p.sql_types, qprefix) - p.cgen.insert_before('char* ${qprefix}params[$p.sql_i]; -$params_gen - -void* ${qprefix}res = PQexecParams(db.conn, "$q", $p.sql_i, 0, ${qprefix}params, 0, 0, 0) ; -array_pg__Row ${qprefix}rows = pg__res_to_rows(${qprefix}res); - -// TODO preallocate -array ${qprefix}arr_$tmp = new_array(0, 0, sizeof($table_name)); -for (int i = 0; i < ${qprefix}rows.len; i++) { - pg__Row ${qprefix}row = *(pg__Row*)array_get(${qprefix}rows, i); - $table_name ${qprefix}$tmp; - ${obj_gen.str()} - _PUSH(&${qprefix}arr_$tmp, ${qprefix}$tmp, ${tmp}2, $table_name); -} -') - p.cgen.resetln('${qprefix}arr_$tmp') - } - } - if n == 'count' { - return 'int' - } - else if query_one { - opt_type := 'Option_$table_name' - p.cgen.typedefs << 'typedef Option $opt_type;' - p.table.register_builtin(opt_type) - return opt_type - } - else { - p.register_array('array_$table_name') - return 'array_$table_name' - } -} - -// `db.insert(user)` -fn (p mut Parser) insert_query(fn_ph int) { - p.check_name() - p.check(.lpar) - var_name := p.check_name() - p.check(.rpar) - var := p.find_var(var_name) or { - return - } - typ := p.table.find_type(var.typ) - mut fields := []Var - for i, field in typ.fields { - if field.typ != 'string' && field.typ != 'int' { - continue - } - fields << field - } - if fields.len == 0 { - p.error('V orm: insert: empty fields in `$var.typ`') - } - if fields[0].name != 'id' { - p.error('V orm: `id int` must be the first field in `$var.typ`') - } - table_name := var.typ - mut sfields := '' // 'name, city, country' - mut params := '' // params[0] = 'bob'; params[1] = 'Vienna'; - mut vals := '' // $1, $2, $3... - mut nr_vals := 0 - for i, field in fields { - if field.name == 'id' { - continue - } - sfields += field.name - vals += '$' + i.str() - nr_vals++ - params += 'params[${i-1}] = ' - if field.typ == 'string' { - params += '$var_name . $field.name .str;\n' - } - else if field.typ == 'int' { - params += 'int_str($var_name . $field.name).str;\n' - } - else { - p.error('V ORM: unsupported type `$field.typ`') - } - if i < fields.len - 1 { - sfields += ', ' - vals += ', ' - } - } - p.cgen.insert_before('char* params[$nr_vals];' + params) - p.cgen.set_placeholder(fn_ph, 'PQexecParams( ') - p.genln('.conn, "insert into $table_name ($sfields) values ($vals)", $nr_vals, -0, params, 0, 0, 0)') -} - -// `db.update User set nr_orders=nr_orders+1` -fn (p mut Parser) update_query(fn_ph int) { - println('update query') - p.check_name() - p.fspace() - table_name := p.check_name() - p.fspace() - typ := p.table.find_type(table_name) - if typ.name == '' { - p.error('unknown type `$table_name`') - } - set := p.check_name() - p.fspace() - if set != 'set' { - p.error('expected `set`') - } - if typ.fields.len == 0 { - p.error('V orm: update: empty fields in `$typ.name`') - } - if typ.fields[0].name != 'id' { - p.error('V orm: `id int` must be the first field in `$typ.name`') - } - field := p.check_name() - p.fspace() - p.check(.assign) - p.fspace() - for f in typ.fields { - if !(f.typ in ['string', 'int', 'bool']) { - println('orm: skipping $f.name') - continue - } - p.register_var({ - f | - is_mut:true, - is_used:true, - is_changed:true - }) - } - mut q := 'update ${typ.name}s set $field=' - p.is_sql = true - set_typ,expr := p.tmp_expr() - p.is_sql = false - // TODO this hack should not be necessary - if set_typ == 'bool' { - if expr.trim_space() == '1' { - q += 'true' - } - else { - q += 'false' - } - } - else { - q += expr - } - // where - if p.tok == .name && p.lit == 'where' { - p.next() - p.fspace() - p.is_sql = true - _,wexpr := p.tmp_expr() - p.is_sql = false - q += ' where ' + wexpr - } - nr_vals := 0 - p.cgen.insert_before('char* params[$nr_vals];') // + params) - p.cgen.set_placeholder(fn_ph, 'PQexecParams( ') - println('update q="$q"') - p.genln('.conn, "$q", $nr_vals, 0, params, 0, 0, 0)') -} - diff --git a/vlib/compiler/scanner.v b/vlib/compiler/scanner.v deleted file mode 100644 index f0f929fbab..0000000000 --- a/vlib/compiler/scanner.v +++ /dev/null @@ -1,1021 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import os -import v.pref - -const ( - single_quote = `\'` - double_quote = `"` - error_context_before = 2 // how many lines of source context to print before the pointer line - error_context_after = 2 // ^^^ same, but after -) - -pub struct Scanner { -mut: - file_path string - text string - pos int - line_nr int - last_nl_pos int // for calculating column - inside_string bool - inter_start bool // for hacky string interpolation TODO simplify - inter_end bool - debug bool - line_comment string - started bool - // vfmt fields TODO move to a separate struct - // fmt_out strings.Builder - fmt_lines []string - // fmt_line string - fmt_indent int - fmt_line_empty bool - // fmt_needs_nl bool - prev_tok TokenKind - fn_name string // needed for @FN - print_line_on_error bool - print_colored_error bool - print_rel_paths_on_error bool - quote byte // which quote is used to denote current string: ' or " - line_ends []int // the positions of source lines ends (i.e. \n signs) - nlines int // total number of lines in the source file that were scanned - is_vh bool // Keep newlines - is_fmt bool // Used only for skipping ${} in strings, since we need literal - // string values when generating formatted code. -} -// new scanner from file. -fn new_scanner_file(file_path string) &Scanner { - if !os.exists(file_path) { - verror("$file_path doesn't exist") - } - mut raw_text := os.read_file(file_path)or{ - verror('scanner: failed to open $file_path') - return 0 - } - // BOM check - if raw_text.len >= 3 { - c_text := raw_text.str - if c_text[0] == 0xEF && c_text[1] == 0xBB && c_text[2] == 0xBF { - // skip three BOM bytes - offset_from_begin := 3 - raw_text = tos(c_text[offset_from_begin], vstrlen(c_text) - offset_from_begin) - } - } - mut s := new_scanner(raw_text) - s.init_fmt() - s.file_path = file_path - return s -} - -// new scanner from string. -fn new_scanner(text string) &Scanner { - return &Scanner{ - text: text - print_line_on_error: true - print_colored_error: true - print_rel_paths_on_error: true - } -} - -// TODO remove once multiple return values are implemented -struct ScanRes { - tok TokenKind - lit string -} - -fn scan_res(tok TokenKind, lit string) ScanRes { - return ScanRes{ - tok:tok - lit:lit - } -} - -fn (s mut Scanner) ident_name() string { - start := s.pos - s.pos++ - for s.pos < s.text.len && (is_name_char(s.text[s.pos]) || s.text[s.pos].is_digit()) { - s.pos++ - } - name := s.text[start..s.pos] - s.pos-- - return name -} - -const( - num_sep = `_` // char used as number separator -) - -fn filter_num_sep(txt byteptr, start int, end int) string { - unsafe { - mut b := malloc(end-start + 1) // add a byte for the endstring 0 - mut i := start - mut i1 := 0 - for i < end { - if txt[i] != num_sep && txt[i] != `o` { - b[i1]=txt[i] - i1++ - } - i++ - } - b[i1]=0 // C string compatibility - return string(b, i1) - } -} - -fn (s mut Scanner) ident_bin_number() string { - mut has_wrong_digit := false - mut first_wrong_digit := `\0` - start_pos := s.pos - s.pos += 2 // skip '0b' - for s.pos < s.text.len { - c := s.text[s.pos] - if !c.is_bin_digit() && c != num_sep { - if (!c.is_digit() && !c.is_letter()) || s.inside_string { - break - } - else if !has_wrong_digit { - has_wrong_digit = true - first_wrong_digit = c - } - } - s.pos++ - } - if start_pos + 2 == s.pos { - s.error('number part of this binary is not provided') - } - else if has_wrong_digit { - s.error('this binary number has unsuitable digit `${first_wrong_digit.str()}`') - } - number := filter_num_sep(s.text.str, start_pos, s.pos) - s.pos-- - return number -} - -fn (s mut Scanner) ident_hex_number() string { - mut has_wrong_digit := false - mut first_wrong_digit := `\0` - start_pos := s.pos - s.pos += 2 // skip '0x' - for s.pos < s.text.len { - c := s.text[s.pos] - if !c.is_hex_digit() && c != num_sep { - if !c.is_letter() || s.inside_string { - break - } - else if !has_wrong_digit { - has_wrong_digit = true - first_wrong_digit = c - } - } - s.pos++ - } - if start_pos + 2 == s.pos { - s.error('number part of this hexadecimal is not provided') - } - else if has_wrong_digit { - s.error('this hexadecimal number has unsuitable digit `${first_wrong_digit.str()}`') - } - number := filter_num_sep(s.text.str, start_pos, s.pos) - s.pos-- - return number -} - -fn (s mut Scanner) ident_oct_number() string { - mut has_wrong_digit := false - mut first_wrong_digit := `\0` - start_pos := s.pos - s.pos += 2 // skip '0o' - for s.pos < s.text.len { - c := s.text[s.pos] - if !c.is_oct_digit() && c != num_sep { - if (!c.is_digit() && !c.is_letter()) || s.inside_string { - break - } - else if !has_wrong_digit { - has_wrong_digit = true - first_wrong_digit = c - } - } - s.pos++ - } - if start_pos + 2 == s.pos { - s.error('number part of this octal is not provided') - } - else if has_wrong_digit { - s.error('this octal number has unsuitable digit `${first_wrong_digit.str()}`') - } - number := filter_num_sep(s.text.str, start_pos, s.pos) - s.pos-- - return number -} - -fn (s mut Scanner) ident_dec_number() string { - mut has_wrong_digit := false - mut first_wrong_digit := `\0` - mut call_method := false // true for, e.g., 12.str(), 12.3.str(), 12e-3.str() - start_pos := s.pos - // scan integer part - for s.pos < s.text.len { - c := s.text[s.pos] - if !c.is_digit() && c != num_sep { - if !c.is_letter() || c in [`e`, `E`] || s.inside_string { - break - } - else if !has_wrong_digit { - has_wrong_digit = true - first_wrong_digit = c - } - } - s.pos++ - } - // e.g. 1..9 - // we just return '1' and don't scan '..9' - if s.expect('..', s.pos) { - number := filter_num_sep(s.text.str, start_pos, s.pos) - s.pos-- - return number - } - // scan fractional part - if s.pos < s.text.len && s.text[s.pos] == `.` { - s.pos++ - if s.pos < s.text.len { - if s.text[s.pos].is_digit() { - for s.pos < s.text.len { - c := s.text[s.pos] - if !c.is_digit() { - if !c.is_letter() || c in [`e`, `E`] || s.inside_string { - if c == `.` && s.pos + 1 < s.text.len && !s.text[s.pos+1].is_digit() && s.text[s.pos+1] != `)` { - call_method = true - } - break - } - else if !has_wrong_digit { - has_wrong_digit = true - first_wrong_digit = c - } - } - s.pos++ - } - } - else if !(s.text[s.pos] in [`)`, `e`, `E`]) { - call_method = true - s.pos-- - } - } - } - // scan exponential part - mut has_exponential_part := false - if s.expect('e', s.pos) || s.expect('E', s.pos) { - s.pos++ - exp_start_pos := s.pos - if s.pos < s.text.len && s.text[s.pos] in [`-`, `+`] { - s.pos++ - } - for s.pos < s.text.len { - c := s.text[s.pos] - if !c.is_digit() { - if !c.is_letter() || s.inside_string { - if c == `.` && s.pos + 1 < s.text.len && !s.text[s.pos+1].is_digit() && s.text[s.pos+1] != `)` { - call_method = true - } - break - } - else if !has_wrong_digit { - has_wrong_digit = true - first_wrong_digit = c - } - } - s.pos++ - } - if exp_start_pos == s.pos { - s.error('exponent has no digits') - } - has_exponential_part = true - } - // error check: 1.23.4, 123.e+3.4 - if s.pos < s.text.len && s.text[s.pos] == `.` && !call_method { - if has_exponential_part { - s.error('exponential part should be integer') - } - else { - s.error('too many decimal points in number') - } - } - if has_wrong_digit { - s.error('this number has unsuitable digit `${first_wrong_digit.str()}`') - } - number := filter_num_sep(s.text.str, start_pos, s.pos) - s.pos-- - return number -} - -fn (s mut Scanner) ident_number() string { - if s.expect('0b', s.pos) { - return s.ident_bin_number() - } - else if s.expect('0x', s.pos) { - return s.ident_hex_number() - } - else if s.expect('0o', s.pos) { - return s.ident_oct_number() - } - else { - return s.ident_dec_number() - } -} - -fn (s mut Scanner) skip_whitespace() { - // if s.is_vh { println('vh') return } - for s.pos < s.text.len && s.text[s.pos].is_space() { - if is_nl(s.text[s.pos]) && s.is_vh { - return - } - // Count \r\n as one line - if is_nl(s.text[s.pos]) && !s.expect('\r\n', s.pos - 1) { - s.inc_line_number() - } - s.pos++ - } -} - -fn (s mut Scanner) end_of_file() ScanRes { - s.pos = s.text.len - s.inc_line_number() - return scan_res(.eof, '') -} - -fn (s mut Scanner) scan() ScanRes { - // if s.line_comment != '' { - // s.fgenln('// LC "$s.line_comment"') - // s.line_comment = '' - // } - if s.started { - s.pos++ - } - s.started = true - if s.pos >= s.text.len { - return s.end_of_file() - } - if !s.inside_string { - s.skip_whitespace() - } - // End of $var, start next string - if s.inter_end { - if s.text[s.pos] == s.quote { - s.inter_end = false - return scan_res(.string, '') - } - s.inter_end = false - return scan_res(.string, s.ident_string()) - } - s.skip_whitespace() - // end of file - if s.pos >= s.text.len { - return s.end_of_file() - } - // handle each char - c := s.text[s.pos] - mut nextc := `\0` - if s.pos + 1 < s.text.len { - nextc = s.text[s.pos + 1] - } - // name or keyword - if is_name_char(c) { - name := s.ident_name() - // tmp hack to detect . in ${} - // Check if not .eof to prevent panic - next_char := if s.pos + 1 < s.text.len { s.text[s.pos + 1] } else { `\0` } - if is_key(name) { - return scan_res(key_to_token(name), '') - } - // 'asdf $b' => "b" is the last name in the string, dont start parsing string - // at the next ', skip it - if s.inside_string { - if next_char == s.quote { - s.inter_end = true - s.inter_start = false - s.inside_string = false - } - } - // end of `$expr` - // allow `'$a.b'` and `'$a.c()'` - if s.inter_start && next_char != `.` && next_char != `(` { - s.inter_end = true - s.inter_start = false - } - if s.pos == 0 && next_char == ` ` { - // If a single letter name at the start of the file, increment - // Otherwise the scanner would be stuck at s.pos = 0 - s.pos++ - } - return scan_res(.name, name) - } - // `123`, `.123` - else if c.is_digit() || (c == `.` && nextc.is_digit()) { - if !s.inside_string { - // In C ints with `0` prefix are octal (in V they're decimal), so discarding heading zeros is needed. - mut start_pos := s.pos - for start_pos < s.text.len && s.text[start_pos] == `0` { - start_pos++ - } - mut prefix_zero_num := start_pos - s.pos // how many prefix zeros should be jumped - // for 0b, 0o, 0x the heading zero shouldn't be jumped - if start_pos == s.text.len || (c == `0` && !s.text[start_pos].is_digit()) { - prefix_zero_num-- - } - s.pos += prefix_zero_num // jump these zeros - } - num := s.ident_number() - return scan_res(.number, num) - } - // Handle `'$fn()'` - if c == `)` && s.inter_start { - s.inter_end = true - s.inter_start = false - next_char := if s.pos + 1 < s.text.len { s.text[s.pos + 1] } else { `\0` } - if next_char == s.quote { - s.inside_string = false - } - return scan_res(.rpar, '') - } - // all other tokens - match c { - `+` { - if nextc == `+` { - s.pos++ - return scan_res(.inc, '') - } - else if nextc == `=` { - s.pos++ - return scan_res(.plus_assign, '') - } - return scan_res(.plus, '') - } - `-` { - if nextc == `-` { - s.pos++ - return scan_res(.dec, '') - } - else if nextc == `=` { - s.pos++ - return scan_res(.minus_assign, '') - } - return scan_res(.minus, '') - } - `*` { - if nextc == `=` { - s.pos++ - return scan_res(.mult_assign, '') - } - return scan_res(.mul, '') - } - `^` { - if nextc == `=` { - s.pos++ - return scan_res(.xor_assign, '') - } - return scan_res(.xor, '') - } - `%` { - if nextc == `=` { - s.pos++ - return scan_res(.mod_assign, '') - } - return scan_res(.mod, '') - } - `?` { - return scan_res(.question, '') - } - single_quote, double_quote { - return scan_res(.string, s.ident_string()) - } - `\`` { - // ` // apostrophe balance comment. do not remove - return scan_res(.chartoken, s.ident_char()) - } - `(` { - return scan_res(.lpar, '') - } - `)` { - return scan_res(.rpar, '') - } - `[` { - return scan_res(.lsbr, '') - } - `]` { - return scan_res(.rsbr, '') - } - `{` { - // Skip { in `${` in strings - if s.inside_string { - return s.scan() - } - return scan_res(.lcbr, '') - } - `$` { - if s.inside_string { - return scan_res(.str_dollar, '') - } - else { - return scan_res(.dollar, '') - } - } - `}` { - // s = `hello $name !` - // s = `hello ${name} !` - if s.inside_string { - s.pos++ - if s.text[s.pos] == s.quote { - s.inside_string = false - return scan_res(.string, '') - } - return scan_res(.string, s.ident_string()) - } - else { - return scan_res(.rcbr, '') - } - } - `&` { - if nextc == `=` { - s.pos++ - return scan_res(.and_assign, '') - } - if nextc == `&` { - s.pos++ - return scan_res(.and, '') - } - return scan_res(.amp, '') - } - `|` { - if nextc == `|` { - s.pos++ - return scan_res(.logical_or, '') - } - if nextc == `=` { - s.pos++ - return scan_res(.or_assign, '') - } - return scan_res(.pipe, '') - } - `,` { - return scan_res(.comma, '') - } - `@` { - s.pos++ - name := s.ident_name() - // @FN => will be substituted with the name of the current V function - // @VEXE => will be substituted with the path to the V compiler - // @FILE => will be substituted with the path of the V source file - // @LINE => will be substituted with the V line number where it appears (as a string). - // @COLUMN => will be substituted with the column where it appears (as a string). - // @VHASH => will be substituted with the shortened commit hash of the V compiler (as a string). - // This allows things like this: - // println( 'file: ' + @FILE + ' | line: ' + @LINE + ' | fn: ' + @FN) - // ... which is useful while debugging/tracing - if name == 'FN' { - return scan_res(.string, s.fn_name) - } - if name == 'VEXE' { - vexe := pref.vexe_path() - return scan_res(.string, cescaped_path( vexe ) ) - } - if name == 'FILE' { - return scan_res(.string, cescaped_path(os.real_path(s.file_path))) - } - if name == 'LINE' { - return scan_res(.string, (s.line_nr + 1).str()) - } - if name == 'COLUMN' { - return scan_res(.string, (s.current_column()).str()) - } - if name == 'VHASH' { - return scan_res(.string, vhash()) - } - if !is_key(name) { - s.error('@ must be used before keywords (e.g. `@type string`)') - } - return scan_res(.name, name) - } - /* - case `\r`: - if nextc == `\n` { - s.pos++ - s.last_nl_pos = s.pos - return scan_res(.nl, '') - } - } - case `\n`: - s.last_nl_pos = s.pos - return scan_res(.nl, '') - } - */ - - `.` { - if nextc == `.` { - s.pos++ - if s.text[s.pos + 1] == `.` { - s.pos++ - return scan_res(.ellipsis, '') - } - return scan_res(.dotdot, '') - } - return scan_res(.dot, '') - } - `#` { - start := s.pos + 1 - s.ignore_line() - if nextc == `!` { - // treat shebang line (#!) as a comment - s.line_comment = s.text[start + 1..s.pos].trim_space() - // s.fgenln('// shebang line "$s.line_comment"') - return s.scan() - } - hash := s.text[start..s.pos] - return scan_res(.hash, hash.trim_space()) - } - `>` { - if nextc == `=` { - s.pos++ - return scan_res(.ge, '') - } - else if nextc == `>` { - if s.pos + 2 < s.text.len && s.text[s.pos + 2] == `=` { - s.pos += 2 - return scan_res(.righ_shift_assign, '') - } - s.pos++ - return scan_res(.righ_shift, '') - } - else { - return scan_res(.gt, '') - } - } - 0xE2 { - // case `≠`: - if nextc == 0x89 && s.text[s.pos + 2] == 0xA0 { - s.pos += 2 - return scan_res(.ne, '') - } - // ⩽ - else if nextc == 0x89 && s.text[s.pos + 2] == 0xBD { - s.pos += 2 - return scan_res(.le, '') - } - // ⩾ - else if nextc == 0xA9 && s.text[s.pos + 2] == 0xBE { - s.pos += 2 - return scan_res(.ge, '') - } - } - `<` { - if nextc == `=` { - s.pos++ - return scan_res(.le, '') - } - else if nextc == `<` { - if s.pos + 2 < s.text.len && s.text[s.pos + 2] == `=` { - s.pos += 2 - return scan_res(.left_shift_assign, '') - } - s.pos++ - return scan_res(.left_shift, '') - } - else if nextc == `-` { - s.pos++ - println("GOT ARR") - return scan_res(.left_arrow, '') - } - else { - return scan_res(.lt, '') - } - } - `=` { - if nextc == `=` { - s.pos++ - return scan_res(.eq, '') - } - else if nextc == `>` { - s.pos++ - return scan_res(.arrow, '') - } - else { - return scan_res(.assign, '') - } - } - `:` { - if nextc == `=` { - s.pos++ - return scan_res(.decl_assign, '') - } - else { - return scan_res(.colon, '') - } - } - `;` { - return scan_res(.semicolon, '') - } - `!` { - if nextc == `=` { - s.pos++ - return scan_res(.ne, '') - } - else { - return scan_res(.not, '') - } - } - `~` { - return scan_res(.bit_not, '') - } - `/` { - if nextc == `=` { - s.pos++ - return scan_res(.div_assign, '') - } - if nextc == `/` { - start := s.pos + 1 - s.ignore_line() - s.line_comment = s.text[start + 1..s.pos] - s.line_comment = s.line_comment.trim_space() - if s.is_fmt { - s.pos-- // fix line_nr, \n was read, and the comment is marked on the next line - s.line_nr-- - return scan_res(.line_comment, s.line_comment) - } - // s.fgenln('// ${s.prev_tok.str()} "$s.line_comment"') - // Skip the comment (return the next token) - return s.scan() - } - // Multiline comments - if nextc == `*` { - start := s.pos - mut nest_count := 1 - // Skip comment - for nest_count > 0 { - s.pos++ - if s.pos >= s.text.len { - s.line_nr-- - s.error('comment not terminated') - } - if s.text[s.pos] == `\n` { - s.inc_line_number() - continue - } - if s.expect('/*', s.pos) { - nest_count++ - continue - } - if s.expect('*/', s.pos) { - nest_count-- - } - } - s.pos++ - end := s.pos + 1 - comment := s.text[start..end] - if s.is_fmt { - s.line_comment = comment - return scan_res(.mline_comment, s.line_comment) - } - // Skip if not in fmt mode - return s.scan() - } - return scan_res(.div, '') - } - else { - }} - $if windows { - if c == `\0` { - return s.end_of_file() - } - } - s.error('invalid character `${c.str()}`') - return s.end_of_file() -} - -fn (s &Scanner) current_column() int { - return s.pos - s.last_nl_pos -} - -fn (s Scanner) count_symbol_before(p int, sym byte) int { - mut count := 0 - for i := p; i >= 0; i-- { - if s.text[i] != sym { - break - } - count++ - } - return count -} - -fn (s mut Scanner) ident_string() string { - q := s.text[s.pos] - is_quote := q == single_quote || q == double_quote - is_raw := is_quote && s.text[s.pos - 1] == `r` - if is_quote && !s.inside_string { - s.quote = q - } - // if s.file_path.contains('string_test') { - // println('\nident_string() at char=${s.text[s.pos].str()}') - // println('linenr=$s.line_nr quote= $qquote ${qquote.str()}') - // } - mut start := s.pos - s.inside_string = false - slash := `\\` - for { - s.pos++ - if s.pos >= s.text.len { - break - } - c := s.text[s.pos] - prevc := s.text[s.pos - 1] - // end of string - if c == s.quote && (prevc != slash || (prevc == slash && s.text[s.pos - 2] == slash)) { - // handle '123\\' slash at the end - break - } - if c == `\n` { - s.inc_line_number() - } - // Don't allow \0 - if c == `0` && s.pos > 2 && s.text[s.pos - 1] == slash { - if s.pos < s.text.len - 1 && s.text[s.pos + 1].is_digit() { - } - else { - s.error('0 character in a string literal') - } - } - // Don't allow \x00 - if c == `0` && s.pos > 5 && s.expect('\\x0', s.pos - 3) { - s.error('0 character in a string literal') - } - // ${var} (ignore in vfmt mode) - if c == `{` && prevc == `$` && !is_raw && !s.is_fmt && s.count_symbol_before(s.pos - 2, slash) % 2 == 0 { - s.inside_string = true - // so that s.pos points to $ at the next step - s.pos -= 2 - break - } - // $var - if is_name_char(c) && prevc == `$` && !s.is_fmt && !is_raw && s.count_symbol_before(s.pos - 2, slash) % 2 == 0 { - s.inside_string = true - s.inter_start = true - s.pos -= 2 - break - } - } - mut lit := '' - if s.text[start] == s.quote { - start++ - } - mut end := s.pos - if s.inside_string { - end++ - } - if start > s.pos { - } - else { - lit = s.text[start..end] - } - return lit -} - -fn (s mut Scanner) ident_char() string { - start := s.pos - slash := `\\` - mut len := 0 - for { - s.pos++ - if s.pos >= s.text.len { - break - } - if s.text[s.pos] != slash { - len++ - } - double_slash := s.expect('\\\\', s.pos - 2) - if s.text[s.pos] == `\`` && (s.text[s.pos - 1] != slash || double_slash) { - // ` // apostrophe balance comment. do not remove - if double_slash { - len++ - } - break - } - } - len-- - c := s.text[start + 1..s.pos] - if len != 1 { - u := c.ustring() - if u.len != 1 { - s.error('invalid character literal (more than one character)\n' + 'use quotes for strings, backticks for characters') - } - } - if c == '\\`' { - return '`' - } - // Escapes a `'` character - return if c == "\'" { '\\' + c } else { c } -} - -fn (s &Scanner) expect(want string, start_pos int) bool { - end_pos := start_pos + want.len - if start_pos < 0 || start_pos >= s.text.len { - return false - } - if end_pos < 0 || end_pos > s.text.len { - return false - } - for pos in start_pos .. end_pos { - if s.text[pos] != want[pos - start_pos] { - return false - } - } - return true -} - -fn (s mut Scanner) debug_tokens() { - s.pos = 0 - s.started = false - s.debug = true - fname := s.file_path.all_after(os.path_separator) - println('\n===DEBUG TOKENS $fname===') - for { - res := s.scan() - tok := res.tok - lit := res.lit - print(tok.str()) - if lit != '' { - println(' `$lit`') - } - else { - println('') - } - if tok == .eof { - println('============ END OF DEBUG TOKENS ==================') - break - } - } -} - -fn (s mut Scanner) ignore_line() { - s.eat_to_end_of_line() - s.inc_line_number() -} - -fn (s mut Scanner) eat_to_end_of_line() { - for s.pos < s.text.len && s.text[s.pos] != `\n` { - s.pos++ - } -} - -fn (s mut Scanner) inc_line_number() { - s.last_nl_pos = s.pos - s.line_nr++ - s.line_ends << s.pos - if s.line_nr > s.nlines { - s.nlines = s.line_nr - } -} - -fn (s Scanner) line(n int) string { - mut res := '' - if n >= 0 && n < s.line_ends.len { - nline_start := if n == 0 { 0 } else { s.line_ends[n - 1] } - nline_end := s.line_ends[n] - if nline_start <= nline_end { - res = s.text[nline_start..nline_end] - } - } - return res.trim_right('\r\n').trim_left('\r\n') -} - -fn is_name_char(c byte) bool { - return (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || c == `_` -} - -[inline] -fn is_nl(c byte) bool { - return c == `\r` || c == `\n` -} - -fn contains_capital(s string) bool { - for c in s { - if c >= `A` && c <= `Z` { - return true - } - } - return false -} - -// HTTPRequest bad -// HttpRequest good -fn good_type_name(s string) bool { - if s.len < 4 { - return true - } - for i in 2 .. s.len { - if s[i].is_capital() && s[i - 1].is_capital() && s[i - 2].is_capital() { - return false - } - } - return true -} diff --git a/vlib/compiler/string_expression.v b/vlib/compiler/string_expression.v deleted file mode 100644 index 9c28c03f38..0000000000 --- a/vlib/compiler/string_expression.v +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -fn (p mut Parser) string_expr() { - is_raw := p.tok == .name && p.lit == 'r' - is_cstr := p.tok == .name && p.lit == 'c' - if is_raw || is_cstr { - p.next() - } - str := p.lit - // No ${}, just return a simple string - if p.peek() != .str_dollar || is_raw { - f := if is_raw { cescaped_path(str).replace('"', '\\"') } else { format_str(str) } - // `C.puts('hi')` => `puts("hi");` - /* - Calling a C function sometimes requires a call to a string method - C.fun('ssss'.to_wide()) => fun(string_to_wide(tos3("ssss"))) - */ - - if (p.calling_c && p.peek() != .dot) || is_cstr || (p.pref.translated && p.mod == 'main') { - if p.os == .windows && p.mod == 'ui' { - p.gen('L"$f"') - } - else { - p.gen('"$f"') - } - } - else if p.is_sql { - p.gen("'$str'") - } - else if p.is_js { - p.gen('tos("$f")') - } - else { - p.gen('tos3("$f")') - } - p.next() - if p.scanner.is_fmt && p.tok == .not { - // handle '$age'! - // TODO remove this hack, do this automatically - p.fgen(' ') - p.check(.not) - } - return - } - $if js { - p.error('js backend does not support string formatting yet') - } - p.is_alloc = true // $ interpolation means there's allocation - mut args := '"' - mut format := '"' - mut complex_inter := false // for vfmt - for p.tok == .string{ - // Add the string between %d's - p.lit = p.lit.replace('%', '%%') - format += format_str(p.lit) - p.next() // skip $ - if p.tok != .str_dollar { - continue - } - // Handle .dollar - p.check(.str_dollar) - // If there's no string after current token, it means we are in - // a complex expression (`${...}`) - if p.peek() != .string{ - p.fgen('{') - complex_inter = true - } - // Get bool expr inside a temp var - typ,val_ := p.tmp_expr() - val := val_.trim_space() - args += ', $val' - if typ == 'string' { - // args += '.str' - // printf("%.*s", a.len, a.str) syntax - args += '.len, ${val}.str' - } - if typ == 'ustring' { - args += '.len, ${val}.s.str' - } - if typ == 'bool' { - // args += '.len, ${val}.str' - } - // Custom format? ${t.hour:02d} - custom := p.tok == .colon - if custom { - mut cformat := '' - p.next() - if p.tok == .dot { - cformat += '.' - p.next() - } - if p.tok == .minus { - // support for left aligned formatting - cformat += '-' - p.next() - } - cformat += p.lit // 02 - p.next() - fspec := p.lit // f - cformat += fspec - if fspec == 's' { - // println('custom str F=$cformat | format_specifier: "$fspec" | typ: $typ ') - if typ != 'string' { - p.error('only V strings can be formatted with a :${cformat} format, but you have given "${val}", which has type ${typ}') - } - args = args.all_before_last('${val}.len, ${val}.str') + '${val}.str' - } - format += '%$cformat' - p.next() - } - else { - f := p.typ_to_fmt(typ, 0) - if f == '' { - has_str_method, styp := p.gen_default_str_method_if_missing( typ ) - if has_str_method { - tmp_var := p.get_tmp() - p.cgen.insert_before('string $tmp_var = ${styp}_str(${val});') - args = args.all_before_last(val) + '${tmp_var}.len, ${tmp_var}.str' - format += '%.*s ' - } - else { - p.error('unhandled sprintf format "$typ" ') - } - } - format += f - } - // println('interpolation format is: |${format}| args are: |${args}| ') - } - if complex_inter { - p.fgen('}') - } - - // p.fgen('\'') - // println("hello %d", num) optimization. - if p.cgen.nogen { - return - } - // println: don't allocate a new string, just print it. - $if !windows { - cur_line := p.cgen.cur_line.trim_space() - if cur_line == 'println (' && p.tok != .plus { - p.cgen.resetln(cur_line.replace('println (', 'printf(')) - p.gen('$format\\n$args') - return - } - } - // '$age'! means the user wants this to be a tmp string (uses global buffer, no allocation, - // won't be used again) - // TODO remove this hack, do this automatically - if p.tok == .not { - p.fgen(' ') - p.check(.not) - p.gen('_STR_TMP($format$args)') - } - else { - // Otherwise do len counting + allocation + sprintf - p.gen('_STR($format$args)') - } -} diff --git a/vlib/compiler/struct.v b/vlib/compiler/struct.v deleted file mode 100644 index e6a2f62da1..0000000000 --- a/vlib/compiler/struct.v +++ /dev/null @@ -1,536 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import ( - strings -) -// also unions and interfaces - - -fn (p mut Parser) struct_decl(generic_param_types []string) { - decl_tok_idx := p.cur_tok_index() - is_pub := p.tok == .key_pub - if is_pub { - p.next() - p.fspace() - } - // V can generate Objective C for integration with Cocoa - // `[objc_interface:ParentInterface]` - is_objc := p.attr.starts_with('objc_interface') - objc_parent := if is_objc { p.attr[15..] } else { '' } - // interface, union, struct - is_interface := p.tok == .key_interface - is_union := p.tok == .key_union - is_struct := p.tok == .key_struct - mut cat := key_to_type_cat(p.tok) - if is_objc { - cat = .objc_interface - } - p.next() - p.fspace() - // Get type name - mut name := p.check_name() - if name.contains('_') && !p.pref.translated { - p.error('type names cannot contain `_`') - } - if !p.builtin_mod && !name[0].is_capital() { - p.error('mod=$p.mod struct names must be capitalized: use `struct ${name.capitalize()}`') - } - if is_interface && !name.ends_with('er') && name[0] != `I` { - p.error('interface names temporarily have to end with `er` (e.g. `Speaker`, `Reader`)') - } - mut generic_types := map[string]string - mut is_generic := false - if p.tok == .lt { - p.check(.lt) - for i := 0; ; i++ { - if generic_param_types.len > 0 && i > generic_param_types.len - 1 { - p.error('mismatched generic type params') - } - type_param := p.check_name() - generic_types[type_param] = if generic_param_types.len > 0 { generic_param_types[i] } else { '' } - if p.tok != .comma { - break - } - p.check(.comma) - } - p.check(.gt) - is_generic = true - } - is_generic_instance := is_generic && generic_param_types.len > 0 - is_c := name == 'C' && p.tok == .dot - if is_c { - /* - if !p.pref.building_v && !p.fileis('vlib') { - p.warn('Virtual C structs will soon be removed from the language' + - '\ndefine the C structs and functions in V') - } - */ - p.check(.dot) - name = p.check_name() - cat = .c_struct - if p.attr == 'typedef' { - cat = .c_typedef - } - } - if name.len == 1 && !p.pref.building_v && !p.pref.is_repl { - p.warn('struct names must have more than one character ("$name", len=$name.len, $p.pref.building_v)') - } - if !is_c && !good_type_name(name) { - p.error('bad struct name, e.g. use `HttpRequest` instead of `HTTPRequest`') - } - // Specify full type name - if !is_c && !p.builtin_mod && p.mod != 'main' { - name = p.prepend_mod(name) - } - mut typ := p.table.find_type(name) - if p.pass == .decl && p.table.known_type_fast(typ) { - // if name in reserved_type_param_names { - // p.error('name `$name` is reserved for type parameters') - // } else { - p.error('type `$name` redeclared') - // } - } - if is_objc { - // Forward declaration of an Objective-C interface with `@class` :) - p.gen_typedef('@class $name;') - } - else if !is_c { - kind := if is_union { 'union' } else { 'struct' } - p.gen_typedef('typedef $kind $name $name;') - } - // TODO: handle error - parser_idx := p.v.get_file_parser_index(p.file_path) or { - 0 - } - // if !p.scanner.is_vh { - // parser_idx = p.v.get_file_parser_index(p.file_path) or { panic('cant find parser idx for $p.file_path') } - // } - // Register the type - mut is_ph := false - if typ.is_placeholder { - // Update the placeholder - is_ph = true - typ.name = name - typ.mod = p.mod - typ.is_c = is_c - typ.is_placeholder = false - typ.cat = cat - typ.parent = objc_parent - typ.is_public = is_pub || p.is_vh - typ.is_generic = is_generic && !is_generic_instance - typ.decl_tok_idx = decl_tok_idx - typ.parser_idx = parser_idx - p.table.rewrite_type(typ) - } - else { - typ = Type{ - name: name - mod: p.mod - is_c: is_c - cat: cat - parent: objc_parent - is_public: is_pub || p.is_vh - is_generic: is_generic && !is_generic_instance - decl_tok_idx: decl_tok_idx - parser_idx: parser_idx - } - } - // Struct `C.Foo` declaration, no body - if is_c && is_struct && p.tok != .lcbr { - p.table.register_type(typ) - return - } - // generic struct - if is_generic { - // template - if !is_generic_instance { - p.table.register_type(typ) - p.table.generic_struct_params[typ.name] = generic_types.keys() - // NOTE: remove to store fields in generic struct template - p.skip_block(false) - return - } - // instance - else { - typ.rename_generic_struct(generic_types) - } - } - p.fspace() - p.check(.lcbr) - // Struct fields - mut access_mod := AccessMod.private - // mut is_pub_field := false - // mut is_mut := false - mut names := []string // to avoid dup names TODO alloc perf - mut fmt_max_len := p.table.max_field_len[name] - // println('fmt max len = $max_len nrfields=$typ.fields.len pass=$p.pass') - if (!is_ph && p.first_pass()) || is_generic { - p.table.register_type(typ) - // println('registering 1 nrfields=$typ.fields.len') - } - mut did_gen_something := false - mut used := []AccessMod - mut i := -1 - for p.tok != .rcbr { - i++ - mut new_access_mod := access_mod - if p.tok == .key_pub { - p.fmt_dec() - p.check(.key_pub) - if p.tok == .key_mut { - p.fspace() - new_access_mod = .public_mut - p.next() // skip `mut` - } - else { - new_access_mod = .public - } - if new_access_mod in used { - p.error('structs can only have one `pub:`/`pub mut:`, all public fields have to be grouped') - } - p.check(.colon) - p.fmt_inc() - p.fgen_nl() - } - else if p.tok == .key_mut { - new_access_mod = .private_mut - if new_access_mod in used { - p.error('structs can only have one `mut:`, all private mutable fields have to be grouped') - } - p.fmt_dec() - p.check(.key_mut) - p.check(.colon) - p.fmt_inc() - p.fgen_nl() - } - else if p.tok == .key_global { - new_access_mod = .global - if new_access_mod in used { - p.error('structs can only have one `__global:`, all global fields have to be grouped') - } - p.fmt_dec() - p.check(.key_global) - p.check(.colon) - p.fmt_inc() - p.fgen_nl() - } - if new_access_mod != access_mod { - used << new_access_mod - } - access_mod = new_access_mod - // (mut) user *User - // if p.tok == .plus { - // p.next() - // } - // Check if reserved name - field_name_token_idx := p.cur_tok_index() - field_name := if name != 'Option' && !is_interface { p.table.var_cgen_name(p.check_name()) } else { p.check_name() } - if p.pass == .main { - p.fgen(strings.repeat(` `, fmt_max_len - field_name.len)) - } - // Check dups - if field_name in names { - p.error('duplicate field `$field_name`') - } - if p.scanner.is_fmt && p.pass == .decl && field_name.len > fmt_max_len { - fmt_max_len = field_name.len - } - if !is_c && p.mod != 'os' && contains_capital(field_name) { - p.error('struct fields cannot contain uppercase letters, use snake_case instead') - } - names << field_name - // We are in an interface? - // `run() string` => run is a method, not a struct field - if is_interface { - f := p.interface_method(field_name, name) - if p.first_pass() { - p.add_method(typ.name, f) - } - continue - } - // `pub` access mod - // access_mod := if is_pub_field { AccessMod.public } else { AccessMod.private} - p.fspace() - defer { - if is_generic_instance { - p.generic_dispatch = TypeInst{ - } - } - } - if is_generic_instance { - p.generic_dispatch = TypeInst{ - inst: generic_types - } - } - tt := p.get_type2() - field_type := tt.name - if field_type == name { - p.error_with_token_index('cannot embed struct `$name` in itself (field `$field_name`)', field_name_token_idx) - } - // Register ?option type - if field_type.starts_with('Option_') { - p.gen_typedef('typedef Option $field_type;') - } - p.check_and_register_used_imported_type(field_type) - is_atomic := p.tok == .key_atomic - if is_atomic { - p.next() - } - // `a int = 4` - if p.tok == .assign { - p.next() - def_val_type,expr := p.tmp_expr() - if def_val_type != field_type { - p.error('expected `$field_type` but got `$def_val_type`') - } - // println('pass=$p.pass $typ.name ADDING field=$field_name "$def_val_type" "$expr"') - if !p.first_pass() { - p.table.add_default_val(i, typ.name, expr) - } - } - // [ATTR] - mut attr := '' - if p.tok == .lsbr { - p.fspace() - p.next() - attr = p.check_name() - if p.tok == .colon { - p.check(.colon) - mut val := '' - match p.tok { - .name { - val = p.check_name() - } - .string{ - val = p.check_string() - } - else { - p.error('attribute value should be either name or string') - }} - attr += ':' + val - } - p.check(.rsbr) - } - if attr == 'raw' && field_type != 'string' { - p.error('struct field with attribute "raw" should be of type "string" but got "$field_type"') - } - did_gen_something = true - is_mut := access_mod in [.private_mut, .public_mut, .global] - if p.first_pass() || is_generic { - p.table.add_field(typ.name, field_name, field_type, is_mut, attr, access_mod) - } - p.fgen_nl() // newline between struct fields - } - if p.scanner.is_fmt && p.pass == .decl { - p.table.max_field_len[typ.name] = fmt_max_len - } - // p.fgen_require_nl() - p.check(.rcbr) - if !is_c && !did_gen_something && p.first_pass() { - p.table.add_field(typ.name, '', 'EMPTY_STRUCT_DECLARATION', false, '', .private) - } - p.fgen_nl() - p.fgen_nl() - // p.fgenln('//kek') -} -// `User{ foo: bar }` -// tok == struct name -fn (p mut Parser) struct_init(typ_ string) string { - p.is_struct_init = true - mut typ := typ_ - mut t := p.table.find_type(typ) - if !t.is_public && t.mod != p.mod { - p.error('struct `$t.name` is private') - } - // generic struct init - if p.peek() == .lt { - p.next() - p.check(.lt) - mut type_params := []string - for { - mut type_param := p.check_name() - if type_param in p.generic_dispatch.inst { - type_param = p.generic_dispatch.inst[type_param] - } - type_params << type_param - if p.tok != .comma { - break - } - p.check(.comma) - } - p.dispatch_generic_struct(mut t, type_params) - t = p.table.find_type(t.name) - typ = t.name - } - if p.gen_struct_init(typ, t) { - return typ - } - ptr := typ.contains('*') - mut did_gen_something := false - // Loop thru all struct init keys and assign values - // u := User{age:20, name:'bob'} - // Remember which fields were set, so that we dont have to zero them later - mut inited_fields := []string - peek := p.peek() - if peek == .colon || p.tok == .rcbr { - for p.tok != .rcbr { - field := if typ != 'Option' { p.table.var_cgen_name(p.check_name()) } else { p.check_name() } - if !p.first_pass() && !t.has_field(field) { - p.error('`$t.name` has no field `$field`') - } - if field in inited_fields { - p.error('already initialized field `$field` in `$t.name`') - } - f := t.find_field(field) or { - p.error('no such field: "$field" in type $typ') - break - } - tt := p.table.find_type(f.typ) - if tt.is_flag { - p.error(err_modify_bitfield) - } - inited_fields << field - p.gen_struct_field_init(field) - p.check(.colon) - p.fspace() - p.expected_type = f.typ - p.check_types(p.bool_expression(), f.typ) - if p.tok == .comma { - p.next() - p.fremove_last() - } - if p.tok != .rcbr { - p.gen(',') - } - p.fspace() - did_gen_something = true - p.fgen_nl() // newline between struct fields - } - // If we already set some fields, need to prepend a comma - if t.fields.len != inited_fields.len && inited_fields.len > 0 { - p.gen(',') - } - // Zero values: init all fields (ints to 0, strings to '' etc) - for i, field in t.fields { - sanitized_name := if typ != 'Option' { p.table.var_cgen_name(field.name) } else { field.name } - // println('### field.name') - // Skip if this field has already been assigned to - if sanitized_name in inited_fields { - continue - } - field_typ := field.typ - if !p.builtin_mod && field_typ.ends_with('*') && !p.is_c_struct_init && p.mod != 'os' && - p.mod != 'ui' { - p.warn('reference field `${typ}.${field.name}` must be initialized') - } - // init map fields - if field_typ.starts_with('map_') { - p.gen_struct_field_init(sanitized_name) - p.gen_empty_map(parse_pointer(field_typ[4..])) - inited_fields << sanitized_name - if i != t.fields.len - 1 { - p.gen(',') - } - did_gen_something = true - continue - } - // Did the user provide a default value for this struct field? - // Use it. Otherwise zero it. - def_val := if t.default_vals.len > i && t.default_vals[i] != '' { t.default_vals[i] } else { type_default(field_typ) } - if def_val != '' && def_val != '{0}' { - p.gen_struct_field_init(sanitized_name) - p.gen(def_val) - if i != t.fields.len - 1 { - p.gen(',') - } - did_gen_something = true - } - } - } - // Point{3,4} syntax - else { - mut T := p.table.find_type(typ) - // Aliases (TODO Hack, implement proper aliases) - if T.fields.len == 0 && T.parent != '' { - T = p.table.find_type(T.parent) - } - for i, ffield in T.fields { - expr_typ := p.bool_expression() - if !p.check_types_no_throw(expr_typ, ffield.typ) { - p.error('field value #${i+1} `$ffield.name` has type `$ffield.typ`, got `$expr_typ` ') - } - tt := p.table.find_type(ffield.typ) - if tt.is_flag { - p.error(err_modify_bitfield) - } - if i < T.fields.len - 1 { - if p.tok != .comma { - p.error('too few values in `$typ` literal (${i+1} instead of $T.fields.len)') - } - p.gen(',') - p.next() - } - } - // Allow `user := User{1,2,3,}` - // The final comma will be removed by vfmt, since we are not calling `p.fgen()` - if p.tok == .comma { - p.next() - } - if p.tok != .rcbr { - p.error('too many fields initialized: `$typ` has $T.fields.len field(s)') - } - did_gen_something = true - } - if !did_gen_something { - p.gen('EMPTY_STRUCT_INITIALIZATION') - } - p.gen('}') - if ptr && !p.is_js { - p.gen(', sizeof($t.name))') - } - p.check(.rcbr) - p.is_struct_init = false - p.is_c_struct_init = false - return typ -} - -fn (t mut Type) rename_generic_struct(generic_types map[string]string) { - t.name = t.name + '_T' - for _, v in generic_types { - t.name = t.name + '_' + type_to_safe_str(v) - } -} - -fn (p mut Parser) dispatch_generic_struct(t mut Type, type_params []string) { - mut generic_types := map[string]string - if t.name in p.table.generic_struct_params { - mut i := 0 - for _, v in p.table.generic_struct_params[t.name] { - generic_types[v] = type_params[i] - i++ - } - t.rename_generic_struct(generic_types) - if p.table.known_type(t.name) { - return - } - p.cgen.typedefs << 'typedef struct $t.name $t.name;\n' - } - mut gp := p.v.parsers[t.parser_idx] - gp.is_vgen = true - saved_state := p.save_state() - p.clear_state(false, true) - gp.token_idx = t.decl_tok_idx - // FIXME: TODO: why are tokens cleared? - if gp.tokens.len == 0 { - gp.scanner.pos = 0 - gp.scan_tokens() - } - gp.next() - gp.struct_decl(type_params) - p.cgen.lines_extra << p.cgen.lines - p.restore_state(saved_state, false, true) -} - diff --git a/vlib/compiler/table.v b/vlib/compiler/table.v deleted file mode 100644 index 0baba45b30..0000000000 --- a/vlib/compiler/table.v +++ /dev/null @@ -1,1185 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import strings - -struct Table { -pub mut: - typesmap map[string]Type - consts []Var - fns map[string]Fn - obf_ids map[string]int // obf_ids['myfunction'] == 23 - modules []string // List of all modules registered by the application - imports []string // List of all imports - cflags []CFlag // ['-framework Cocoa', '-lglfw3'] - fn_cnt int // atomic - obfuscate bool - varg_access []VargAccess - // enum_vals map[string][]string - // names []Name - max_field_len map[string]int // for vfmt: max_field_len['Parser'] == 12 - generic_struct_params map[string][]string - tuple_variants map[string][]string // enum( Bool(BoolExpr) ) - sum_types map[string][]string // SumType -> [Variants] -} - -struct VargAccess { - fn_name string - tok_idx int - index int -} - -enum NameCategory { - constant - mod - var - typ -} - -struct Name { - cat NameCategory - idx int // e.g. typ := types[name.idx] -} - -enum AccessMod { - private // private immutable - private_mut // private mutable - public // public immutable (readonly) - public_mut // public, but mutable only in this module - global // public and mutable both inside and outside (not recommended to use, that's why it's so verbose) -} - -fn (a []AccessMod) contains(b AccessMod) bool { - for elm in a { - if elm == b { - return true - } - } - return false -} - -enum TypeCategory { - builtin - struct_ - func // 2 - interface_ - enum_ - union_ // 5 - c_struct - c_typedef - objc_interface // 8 Objective C @interface - array - alias // `type myint int` -} - -struct Var { -pub mut: - typ string - name string - idx int // index in the local_vars array - is_arg bool - is_const bool - args []Var // function args - attr string // [json] etc - is_mut bool - is_alloc bool - is_returned bool - ptr bool - ref bool - parent_fn string // Variables can only be defined in functions - mod string // module where this var is stored - access_mod AccessMod - is_global bool // __global (translated from C only) - is_used bool - is_changed bool - scope_level int - is_c bool // todo remove once `typ` is `Type`, not string - is_moved bool - line_nr int - token_idx int // this is a token index, which will be used by error reporting - is_for_var bool - is_public bool // for consts -} - -struct Type { -pub mut: - mod string - name string - cat TypeCategory - is_public bool - fields []Var - methods []Fn - parent string - func Fn // For cat == FN (type myfn fn()) - is_c bool // `C.FILE` - enum_vals []string - gen_types []string - default_vals []string // `struct Foo { bar int = 2 }` - parser_idx int - decl_tok_idx int - // `is_placeholder` 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. - is_placeholder bool - gen_str bool // needs `.str()` method generation - is_flag bool // enum bitfield flag - // max_field_len int - is_generic bool - ctype_names []string -} - -struct TypeNode { -mut: - next &TypeNode - typ Type -} - -/* -// For debugging types -pub fn (t Type) str() string { - mut s := 'type "$t.name" {' - if t.fields.len > 0 { - // s += '\n $t.fields.len fields:\n' - for field in t.fields { - s += '\n $field.name $field.typ' - } - s += '\n' - } - if t.methods.len > 0 { - // s += '\n $t.methods.len methods:\n' - for method in t.methods { - s += '\n ${method.str()}' - } - s += '\n' - } - s += '}\n' - return s -} -*/ - - -const ( - c_reserved = ['delete', 'exit', 'unix', - // 'print', - // 'ok', - 'error', 'malloc', -//'calloc', -'free', 'panic', - // Full list of C reserved words, from: https://en.cppreference.com/w/c/keyword - 'auto', 'char', 'default', 'do', 'double', 'extern', 'float', 'inline', 'int', 'long', 'register', 'restrict', 'short', 'signed', 'sizeof', 'static', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while', ] -) -// This is used for debugging only -pub fn (f Fn) str() string { - t := Table{ - } - str_args := f.str_args(t) - return '${f.name}($str_args) $f.typ' -} - -pub 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 ( - integer_types = ['int', 'i8', 'char', 'byte', 'i16', 'u16', 'u32', 'i64', 'u64'] - float_types = ['f32', 'f64'] - reserved_type_param_names = ['R', 'S', 'T', 'U', 'W'] - pointer_types = ['byte*', 'byteptr', 'char*', 'charptr', 'void*', 'voidptr', 'voidptr*', 'intptr'] - builtin_types = ['int', 'i8', 'char', 'byte', 'i16', 'u16', 'u32', 'i64', 'u64', - 'f64', 'f32', 'byteptr', 'charptr', 'voidptr', 'intptr', 'string', 'ustring'] -) - -fn is_number_type(typ string) bool { - return typ in integer_types || typ in float_types -} - -fn is_integer_type(typ string) bool { - return typ in integer_types -} - -fn is_float_type(typ string) bool { - return typ in float_types -} - -fn is_primitive_type(typ string) bool { - return is_number_type(typ) || typ == 'string' -} - -fn is_pointer_type(typ string) bool { - return typ in pointer_types -} - -/* -fn (t mut Table) register_enum_val(typ, val string) { - if t.enum_vals.len == 0 { - t.enum_vals = [val] - } -} -*/ - - -fn new_table(obfuscate bool) &Table { - mut t := &Table{ - obfuscate: obfuscate - // enum_vals: map[string][]string - - } - t.register_builtin('int') - t.register_builtin('size_t') - t.register_type_with_parent('i8', 'int') - t.register_type_with_parent('byte', 'int') - t.register_type_with_parent('char', 'int') // for C functions only, to avoid warnings - t.register_type_with_parent('i16', 'int') - t.register_type_with_parent('u16', 'u32') - t.register_type_with_parent('u32', 'int') - t.register_type_with_parent('i64', 'int') - t.register_type_with_parent('u64', 'u32') - t.register_builtin('byteptr') - t.register_builtin('charptr') - t.register_builtin('intptr') - t.register_builtin('f32') - t.register_builtin('f64') - t.register_builtin('rune') - t.register_builtin('bool') - t.register_builtin('void') - t.register_builtin('voidptr') - t.register_builtin('va_list') - for c in reserved_type_param_names { - t.register_builtin(c) - } - t.register_const('stdin', 'int', 'main', true) - t.register_const('stdout', 'int', 'main', true) - t.register_const('stderr', 'int', 'main', true) - t.register_const('errno', 'int', 'main', true) - t.register_type_with_parent('map_string', 'map') - t.register_type_with_parent('map_int', 'map') - return t -} - -// If `name` is a reserved C keyword, returns `v_name` instead. -fn (t &Table) var_cgen_name(name string) string { - if name in c_reserved { - return 'v_$name' - } - else { - return name - } -} - -fn (t mut Table) register_module(mod string) { - if mod in t.modules { - return - } - t.modules << mod -} - -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_mod(mod string) bool { - return mod in table.modules -} - -fn (t mut Table) register_const(name, typ, mod string, is_pub bool) { - t.consts << Var{ - name: name - typ: typ - is_const: true - mod: mod - idx: -1 - is_public: is_pub - } -} - -// Only for translated code -fn (p mut Parser) register_global(name, typ string) { - p.table.consts << Var{ - name: name - typ: typ - is_const: true - is_global: true - mod: p.mod - is_mut: true - idx: -1 - } -} - -// Only for module functions, not methods. -// That's why searching by fn name works. -fn (t mut Table) register_fn(new_fn Fn) { - t.fns[new_fn.name] = new_fn -} - -fn (table &Table) known_type(typ_ string) bool { - mut typ := typ_ - // 'byte*' => look up 'byte', but don't mess up fns - if typ.ends_with('*') && !typ.contains(' ') { - typ = typ.replace('*', '') - } - t := table.typesmap[typ] - return t.name.len > 0 && !t.is_placeholder -} - -fn (table &Table) known_type_fast(t &Type) bool { - return t.name != '' && !t.is_placeholder -} - -fn (t &Table) find_fn(name string) ?Fn { - f := t.fns[name] - if f.name.str != 0 { - // TODO - return f - } - return none -} - -fn (t &Table) find_fn_is_script(name string, is_script bool) ?Fn { - mut f := t.fns[name] - if f.name.str != 0 { - // TODO - return f - } - // V script? Try os module. - if is_script { - println('trying replace $name') - f = t.fns[name.replace('main__', 'os__')] - if f.name.str != 0 { - return f - } - } - return none -} - -fn (t &Table) known_fn(name string) bool { - _ = t.find_fn(name) or { - return false - } - return true -} - -fn (p &Parser) known_fn_in_mod(name string) bool { - existing_fn := p.table.find_fn(name) or { - return false - } - if existing_fn.mod == p.mod || existing_fn.mod == 'builtin' { - return true - } - return false -} - -fn (t &Table) known_const(name string) bool { - _ = t.find_const(name) or { - return false - } - return true -} - -fn (t mut Table) register_builtin(typ string) { - if typ.len == 0 { - return - } - if typ in t.typesmap { - return - } - t.typesmap[typ] = Type{ - name: typ - is_public: true - } -} - -fn (p mut Parser) register_type_with_parent(strtyp, parent string) { - typ := Type{ - name: strtyp - parent: parent - mod: p.mod - is_public: true - } - p.table.register_type(typ) -} - -fn (t mut Table) register_type_with_parent(typ, parent string) { - if typ.len == 0 { - return - } - t.typesmap[typ] = Type{ - name: typ - parent: parent - is_public: true - // mod: mod - - } -} - -fn (t mut Table) register_type(typ Type) { - if typ.name.len == 0 { - return - } - t.typesmap[typ.name] = typ -} - -fn (t mut Table) rewrite_type(typ Type) { - if typ.name.len == 0 { - return - } - t.typesmap[typ.name] = typ -} - -fn (table mut Table) add_field(type_name, field_name, field_type string, is_mut bool, attr string, access_mod AccessMod) { - if type_name == '' { - print_backtrace() - verror('add_field: empty type') - } - mut t := table.typesmap[type_name] - t.fields << Var{ - name: field_name - typ: field_type - is_mut: is_mut - attr: attr - parent_fn: type_name // Name of the parent type - - access_mod: access_mod - } - table.typesmap[type_name] = t -} - -fn (table mut Table) add_default_val(idx int, type_name, val_expr string) { - mut t := table.typesmap[type_name] - if t.default_vals.len == 0 { - t.default_vals = [''].repeat(t.fields.len) - } - t.default_vals[idx] = val_expr - table.typesmap[type_name] = t -} - -fn (t &Type) has_field(name string) bool { - _ = t.find_field(name) or { - return false - } - return true -} - -fn (t &Type) has_enum_val(name string) bool { - return name in t.enum_vals -} - -fn (t &Type) find_field(name string) ?Var { - for field in t.fields { - if field.name == name { - return field - } - } - return none -} - -fn (table &Table) type_has_field(typ &Type, name string) bool { - _ = table.find_field(typ, name) or { - return false - } - return true -} - -fn (table &Table) find_field(typ &Type, name string) ?Var { - for field in typ.fields { - if field.name == name { - return field - } - } - if typ.parent != '' { - parent := table.find_type(typ.parent) - for field in parent.fields { - if field.name == name { - return field - } - } - } - return none -} - -fn (p mut Parser) add_method(type_name string, f Fn) { - if !p.first_pass() && f.name != 'str' { - return - } - if type_name == '' { - print_backtrace() - verror('add_method: empty type') - } - // TODO table.typesmap[type_name].methods << f - mut t := p.table.typesmap[type_name] - if f.name != 'str' && f in t.methods { - p.error('redefinition of method `${type_name}.$f.name`') - } - t.methods << f - p.table.typesmap[type_name] = t -} - -fn (t &Type) has_method(name string) bool { - _ = t.find_method(name) or { - return false - } - return true -} - -fn (table &Table) type_has_method(typ &Type, name string) bool { - _ = table.find_method(typ, name) or { - return false - } - return true -} - -fn (table &Table) find_method(typ &Type, name string) ?Fn { - t := table.typesmap[typ.name] - for method in t.methods { - if method.name == name { - return method - } - } - if typ.parent != '' { - parent := table.find_type(typ.parent) - for method in parent.methods { - if method.name == name { - return method - } - } - return none - } - return none -} - -fn (t &Type) find_method(name string) ?Fn { - // println('$t.name find_method($name) methods.len=$t.methods.len') - for method in t.methods { - // println('method=$method.name') - if method.name == name { - return method - } - } - return none -} - -fn (table mut Table) add_gen_type(type_name, gen_type string) { - mut t := table.typesmap[type_name] - if gen_type in t.gen_types { - return - } - t.gen_types << gen_type - table.typesmap[type_name] = t -} - -fn (p &Parser) find_type(name string) Type { - typ := p.table.find_type(name) - if typ.name == '' { - return p.table.find_type(p.prepend_mod(name)) - } - return typ -} - -fn (t &Table) find_type(name_ string) Type { - mut name := name_ - if name.ends_with('*') && !name.contains(' ') { - name = name.replace('*', '') - } - if !(name in t.typesmap) { - // println('ret Type') - return Type{ - } - } - return t.typesmap[name] -} - -fn (p mut Parser) check_types2(got_, expected_ string, throw bool) bool { - //if p.fileis('type_test') { - //println('got=$got_ exp=$expected_') - //} - mut got := got_ - mut expected := expected_ - // p.log('check types got="$got" exp="$expected" ') - if p.pref.translated { - return true - } - if got == expected { - return true - } - // generic return type - if expected == '_ANYTYPE_' { - p.cur_fn.typ = got - return true - } - if throw && p.base_type(got) == p.base_type(expected) { - return true - } - // variadic - if expected.starts_with('varg_') { - expected = expected[5..] - } - if got.starts_with('varg_') { - got = got[5..] - } - // fn == 0 temporary - if got == 'int' && expected.ends_with('Fn') { - return true - } - // Allow ints to be used as floats - if got == 'int' && expected == 'f32' { - return true - } - if got == 'int' && expected == 'f64' { - return true - } - if got == 'f64' && expected == 'f32' { - return true - } - if got == 'f32' && expected == 'f64' { - return true - } - // Allow ints to be used as longs - if got == 'int' && expected == 'i64' { - return true - } - if got == 'void*' && expected.starts_with('fn ') { - return true - } - if got.starts_with('[') && expected == 'byte*' { - return true - } - // Todo void* allows everything right now - if got == 'void*' || expected == 'void*' { - // || got == 'cvoid' || expected == 'cvoid' { - return true - } - // TODO only allow numeric consts to be assigned to bytes, and - // throw an error if they are bigger than 255 - if got == 'int' && expected == 'byte' { - return true - } - if got == 'byteptr' && expected == 'byte*' { - return true - } - if got == 'byte*' && expected == 'byteptr' { - return true - } - if got == 'charptr' && expected == 'char*' { - return true - } - if got == 'char*' && expected == 'charptr' { - return true - } - if got == 'int' && expected == 'byte*' { - return true - } - // if got=='int' && expected=='voidptr*' { - // return true - // } - // byteptr += int - if got == 'int' && expected in ['byteptr', 'charptr'] { - return true - } - if got == 'Option' && expected.starts_with('Option_') { - return true - } - // lines := new_array - if got == 'array' && expected.starts_with('array_') { - return true - } - // Expected type "Option_os__File", got "os__File" - if expected.starts_with('Option_') && expected.ends_with(stringify_pointer(got)) { - return true - } - // NsColor* return 0 - if expected.ends_with('*') && got == 'int' { - return true - } - // if got == 'T' || got.contains('') { - // return true - // } - // if expected == 'T' || expected.contains('') { - // return true - // } - // TODO fn hack - if got.starts_with('fn ') && (expected.ends_with('fn') || expected.ends_with('Fn')) { - return true - } - if got.starts_with('fn ') && expected.starts_with('fn ') && p.mod == 'gg2' { - return true - } - // Allow pointer arithmetic - if expected == 'void*' && got == 'int' { - return true - } - // if p.fileis('_test') && is_number_type(got) && is_number_type(expected) { - // p.warn('got=$got exp=$expected $p.is_const_literal') - // } - // Allow `myu64 == 1`, `myfloat == 2` etc - if is_integer_type(got) && is_number_type(expected) && p.is_const_literal { - return true - } - if expected == 'integer' { - if is_integer_type(got) { - return true - } - else { - p.error('expected type `$expected`, but got `$got`') - } - } - expected = expected.replace('*', '') - got = got.replace('*', '').replace('ptr', '') - if got != expected { - // Interface check - if expected.ends_with('er') || expected[0] == `I` { - if p.satisfies_interface(expected, got, throw) { - return true - } - } - // Sum type - if expected in p.table.sum_types { - //println('checking sum') - if got in p.table.sum_types[expected] { - //println('yep $expected') - return true - } - } - if !throw { - return false - } - else { - p.error('cannot convert `$got` to `$expected`') - } - } - return true -} - -fn (p mut Parser) base_type(name string) string { - typ := p.find_type(name) - if typ.parent != '' { - return p.base_type(typ.parent) - } - return name -} - -// throw by default -fn (p mut Parser) check_types(got, expected string) bool { - if p.first_pass() { - return true - } - return p.check_types2(got, expected, true) -} - -fn (p mut Parser) check_types_no_throw(got, expected string) bool { - return p.check_types2(got, expected, false) -} - -fn (p mut Parser) check_types_with_token_index(got, expected string, var_token_idx int) { - if !p.check_types2(got, expected, false) { - p.error_with_token_index('expected type `$expected`, but got `$got`', var_token_idx) - } -} - -fn (p mut Parser) satisfies_interface(interface_name, _typ string, throw bool) bool { - int_typ := p.table.find_type(interface_name) - typ := p.table.find_type(_typ) - for method in int_typ.methods { - if !typ.has_method(method.name) { - // if throw { - p.error("type `$_typ` doesn\'t satisfy interface " + '`$interface_name` (method `$method.name` is not implemented)') - // } - return false - } - } - return true -} - -fn (table &Table) is_interface(name string) bool { - if !(name in table.typesmap) { - return false - } - t := table.typesmap[name] - return t.cat == .interface_ -} - -// Do we have fn main()? -fn (t &Table) main_exists() bool { - for _, f in t.fns { - if f.name == 'main__main' { - return true - } - } - return false -} - -fn (t &Table) all_test_function_names() []string { - mut fn_begin_test_name := '' - mut fn_end_test_name := '' - - mut fn_test_names := []string - for _, f in t.fns { - if f.name.contains('__test_') { - fn_test_names << f.name - } - else if f.name.contains('__testsuite_begin') { - fn_begin_test_name = f.name - } - else if f.name.contains('__testsuite_end') { - fn_end_test_name = f.name - } - } - if fn_begin_test_name.len == 0 { - if fn_end_test_name.len > 0 { - fn_test_names << fn_end_test_name - } - return fn_test_names - } - else { - mut res := []string - res << fn_begin_test_name - res << fn_test_names - if fn_end_test_name.len > 0 { - res << fn_end_test_name - } - return res - } -} - -fn (t &Table) find_const(name string) ?Var { - // println('find const l=$t.consts.len') - for c in t.consts { - if c.name == name { - return c - } - } - return none -} - -// ('s', 'string') => 'string s' -// ('nums', '[20]byte') => 'byte nums[20]' -// ('myfn', 'fn(int) string') => 'string (*myfn)(int)' -fn (table &Table) cgen_name_type_pair(name, typ string) string { - // Special case for [10]int - if typ.len > 0 && typ[0] == `[` { - tmp := typ.all_after(']') - size := typ.all_before(']') - return '$tmp $name $size ]' - } - // fn() - else if typ.starts_with('fn (') { - T := table.find_type(typ) - if T.name == '' { - eprintln('function type `$typ` not found') - exit(1) - } - str_args := T.func.str_args(table) - return '$T.func.typ (*$name)( $str_args /*FFF*/ )' - } - // TODO tm hack, do this for all C struct args - else if typ == 'tm' { - return 'struct /*TM*/ tm $name' - } - return '$typ $name' -} - -fn is_valid_int_const(val, typ string) bool { - // x := val.int() - match typ { - 'char'{ - x := val.int() - return 0 <= x && x <= 255 - } - 'byte'{ - x := val.int() - return 0 <= x && x <= 255 - } - 'u16'{ - x := val.u16() - return 0 <= x && x <= 65535 - } - // case 'u32': return 0 <= x && x <= math.MaxU32 - // case 'u64': return 0 <= x && x <= math.MaxU64 - // //////////// - 'i8'{ - x := val.i8() - return -128 <= x && x <= 127 - } - /* - case 'i16': return math.min_i16 <= x && x <= math.max_i16 - case 'int': return math.min_i32 <= x && x <= math.max_i32 - */ - - // case 'i64': - // x64 := val.i64() - // return i64(-(1<<63)) <= x64 && x64 <= i64((1<<63)-1) - else { - return true}} -} - -fn (p mut Parser) typ_to_fmt(typ string, level int) string { - t := p.table.find_type(typ) - if t.cat == .enum_ { - return '%d' - } - match typ { - 'string' { - return '%.*s' - } - // case 'bool': return '%.*s' - 'ustring' { - return '%.*s' - } - 'byte', 'bool', 'int', 'char', 'i16', 'i8' { - return '%d' - } - 'u16', 'u32' { - return '%u' - } - 'f64', 'f32' { - return '%f' - } - 'i64' { - return '%lld' - } - 'u64' { - return '%llu' - } - 'byte*', 'byteptr' { - return '%s' - } - // case 'array_string': return '%s' - // case 'array_int': return '%s' - 'void' { - p.error('cannot interpolate this value') - } - else { - if typ.ends_with('*') { - return '%p' - } - }} - if t.parent != '' && level == 0 { - return p.typ_to_fmt(t.parent, level + 1) - } - return '' -} - -fn type_to_safe_str(typ string) string { - r := typ.replace(' ', '').replace('(', '_').replace(')', '_') - return r -} - -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 (t &Type) contains_field_type(typ string) bool { - if !t.name[0].is_capital() { - return false - } - for field in t.fields { - if field.typ == typ { - return true - } - } - return false -} - -// check for a function / variable / module typo in `name` -fn (p &Parser) identify_typo(name string) string { - // dont check if so short - if name.len < 2 { - return '' - } - name_dotted := mod_gen_name_rev(name.replace('__', '.')) - min_match := 0.50 // for dice coefficient between 0.0 - 1.0 - mut output := '' - // check imported modules - mut n := p.table.find_misspelled_imported_mod(name_dotted, p, min_match) - if n.len > 0 { - output += '\n * module: `$n`' - } - // check consts - n = p.table.find_misspelled_const(name, p, min_match) - if n != '' { - output += '\n * const: `$n`' - } - // check types - typ,type_cat := p.table.find_misspelled_type(name, p, min_match) - if typ.len > 0 { - output += '\n * $type_cat: `$typ`' - } - // check functions - n = p.table.find_misspelled_fn(name, p, min_match) - if n.len > 0 { - output += '\n * function: `$n`' - } - // check function local variables - n = p.find_misspelled_local_var(name_dotted, min_match) - if n.len > 0 { - output += '\n * variable: `$n`' - } - return output -} - -// compare just name part, some items are mod prefied -fn typo_compare_name_mod(a, b, b_mod string) f32 { - if a.len - b.len > 2 || b.len - a.len > 2 { - return 0 - } - auidx := a.index('__') or { - return 0 - } // TODO or {-1} once cgen lines bug is fixed //-1 } - buidx := b.index('__') or { - return 0 - } // -1 } - a_mod := if auidx != -1 { mod_gen_name_rev(a[..auidx]) } else { '' } - a_name := if auidx != -1 { a[auidx + 2..] } else { a } - b_name := if buidx != -1 { b[buidx + 2..] } else { b } - if a_mod.len > 0 && b_mod.len > 0 && a_mod != b_mod { - return 0 - } - return strings.dice_coefficient(a_name, b_name) -} - -// find function with closest name to `name` -fn (table &Table) find_misspelled_fn(name string, p &Parser, min_match f32) string { - mut closest := f32(0) - mut closest_fn := '' - for _, f in table.fns { - if f.name.contains('__') && !p.is_mod_in_scope(f.mod) { - continue - } - c := typo_compare_name_mod(name, f.name, f.mod) - if c > closest { - closest = c - closest_fn = mod_gen_name_rev(f.name.replace('__', '.')) - } - } - return if closest >= min_match { closest_fn } else { '' } -} - -// find imported module with closest name to `name` -fn (table &Table) find_misspelled_imported_mod(name string, p &Parser, min_match f32) string { - mut closest := f32(0) - mut closest_mod := '' - n1 := if name.starts_with('main.') { name[5..] } else { name } - for alias, mod in p.import_table.imports { - c := typo_compare_name_mod(n1, alias, '') - if c > closest { - closest = c - closest_mod = if alias == mod { alias } else { '$alias ($mod)' } - } - } - return if closest >= min_match { closest_mod } else { '' } -} - -// find const with closest name to `name` -fn (table &Table) find_misspelled_const(name string, p &Parser, min_match f32) string { - mut closest := f32(0) - mut closest_const := '' - for cnst in table.consts { - if cnst.name.contains('__') && !p.is_mod_in_scope(cnst.mod) { - continue - } - c := typo_compare_name_mod(name, cnst.name, cnst.mod) - if c > closest { - closest = c - closest_const = mod_gen_name_rev(cnst.name.replace('__', '.')) - } - } - return if closest >= min_match { closest_const } else { '' } -} - -// find type with closest name to `name` -fn (table &Table) find_misspelled_type(name string, p &Parser, min_match f32) (string,string) { - mut closest := f32(0) - mut closest_type := '' - mut type_cat := '' - for _, typ in table.typesmap { - if typ.name.contains('__') && !p.is_mod_in_scope(typ.mod) { - continue - } - c := typo_compare_name_mod(name, typ.name, typ.mod) - if c > closest { - closest = c - closest_type = mod_gen_name_rev(typ.name.replace('__', '.')) - type_cat = type_cat_str(typ.cat) - } - } - if closest >= min_match { - return closest_type,type_cat - } - return '','' -} - -fn type_cat_str(tc TypeCategory) string { - tc_str := match tc { - .builtin{ - 'builtin' - } - .struct_{ - 'struct' - } - .func{ - 'function' - } - .interface_{ - 'interface' - } - .enum_{ - 'enum' - } - .union_{ - 'union' - } - .c_struct{ - 'C struct' - } - .c_typedef{ - 'C typedef' - } - .objc_interface{ - 'obj C interface' - } - .array{ - 'array' - } - .alias{ - 'type alias' - } - else { - 'unknown'}} - return tc_str -} diff --git a/vlib/compiler/token.v b/vlib/compiler/token.v deleted file mode 100644 index 9fbb76846f..0000000000 --- a/vlib/compiler/token.v +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -struct Token { - tok TokenKind // the token number/enum; for quick comparisons - lit string // literal representation of the token - line_nr int // the line number in the source where the token occured - name_idx int // name table index for O(1) lookup - pos int // the position of the token in scanner text -} - -enum TokenKind { - eof - name // user - number // 123 - string // 'foo' - str_inter // 'name=$user.name' - chartoken // `A` - plus - minus - mul - div - mod - xor // ^ - pipe // | - inc // ++ - dec // -- - and // && - logical_or - not - bit_not - question - comma - semicolon - colon - arrow // => - left_arrow // <- - amp - hash - dollar - str_dollar - left_shift - righ_shift - // at // @ - assign // = - decl_assign // := - plus_assign // += - minus_assign // -= - div_assign - mult_assign - xor_assign - mod_assign - or_assign - and_assign - righ_shift_assign - left_shift_assign - // {} () [] - lcbr - rcbr - lpar - rpar - lsbr - rsbr - // == != <= < >= > - eq - ne - gt - lt - ge - le - // comments - line_comment - mline_comment - nl - dot - dotdot - ellipsis - // keywords - keyword_beg - key_as - key_asm - key_assert - key_atomic - key_break - key_const - key_continue - key_defer - key_else - key_embed - key_enum - key_false - key_for - key_fn - key_global - key_go - key_goto - key_if - key_import - key_import_const - key_in - key_interface - // key_it - key_match - key_module - key_mut - key_none - key_return - key_select - key_sizeof - key_offsetof - key_nameof - key_struct - key_switch - key_true - key_type - key_typeof - key_orelse - key_union - key_pub - key_static - key_unsafe - keyword_end -} - -// build_keys genereates a map with keywords' string values: -// Keywords['return'] == .key_return -fn build_keys() map[string]int { - mut res := map[string]int - for t := int(TokenKind.keyword_beg) + 1; t < int(TokenKind.keyword_end); t++ { - key := TokenStr[t] - res[key] = t - } - return res -} - -// TODO remove once we have `enum TokenKind { name('name') if('if') ... }` -fn build_token_str() []string { - mut s := [''].repeat(NrTokens) - s[TokenKind.keyword_beg] = '' - s[TokenKind.keyword_end] = '' - s[TokenKind.eof] = 'eof' - s[TokenKind.name] = 'name' - s[TokenKind.number] = 'number' - s[TokenKind.string] = 'STR' - s[TokenKind.chartoken] = 'char' - s[TokenKind.plus] = '+' - s[TokenKind.minus] = '-' - s[TokenKind.mul] = '*' - s[TokenKind.div] = '/' - s[TokenKind.mod] = '%' - s[TokenKind.xor] = '^' - s[TokenKind.bit_not] = '~' - s[TokenKind.pipe] = '|' - s[TokenKind.hash] = '#' - s[TokenKind.amp] = '&' - s[TokenKind.inc] = '++' - s[TokenKind.dec] = '--' - s[TokenKind.and] = '&&' - s[TokenKind.logical_or] = '||' - s[TokenKind.not] = '!' - s[TokenKind.dot] = '.' - s[TokenKind.dotdot] = '..' - s[TokenKind.ellipsis] = '...' - s[TokenKind.comma] = ',' - // s[TokenKind.at] = '@' - s[TokenKind.semicolon] = ';' - s[TokenKind.colon] = ':' - s[TokenKind.arrow] = '=>' - s[TokenKind.assign] = '=' - s[TokenKind.decl_assign] = ':=' - s[TokenKind.plus_assign] = '+=' - s[TokenKind.minus_assign] = '-=' - s[TokenKind.mult_assign] = '*=' - s[TokenKind.div_assign] = '/=' - s[TokenKind.xor_assign] = '^=' - s[TokenKind.mod_assign] = '%=' - s[TokenKind.or_assign] = '|=' - s[TokenKind.and_assign] = '&=' - s[TokenKind.righ_shift_assign] = '>>=' - s[TokenKind.left_shift_assign] = '<<=' - s[TokenKind.lcbr] = '{' - s[TokenKind.rcbr] = '}' - s[TokenKind.lpar] = '(' - s[TokenKind.rpar] = ')' - s[TokenKind.lsbr] = '[' - s[TokenKind.rsbr] = ']' - s[TokenKind.eq] = '==' - s[TokenKind.ne] = '!=' - s[TokenKind.gt] = '>' - s[TokenKind.lt] = '<' - s[TokenKind.ge] = '>=' - s[TokenKind.le] = '<=' - s[TokenKind.question] = '?' - s[TokenKind.left_shift] = '<<' - s[TokenKind.righ_shift] = '>>' - s[TokenKind.line_comment] = '// line comment' - s[TokenKind.mline_comment] = '/* mline comment */' - s[TokenKind.nl] = 'NLL' - s[TokenKind.dollar] = '$' - s[TokenKind.str_dollar] = '$2' - s[TokenKind.key_assert] = 'assert' - s[TokenKind.key_struct] = 'struct' - s[TokenKind.key_if] = 'if' - // s[TokenKind.key_it] = 'it' - s[TokenKind.key_else] = 'else' - s[TokenKind.key_asm] = 'asm' - s[TokenKind.key_return] = 'return' - s[TokenKind.key_module] = 'module' - s[TokenKind.key_sizeof] = 'sizeof' - s[TokenKind.key_go] = 'go' - s[TokenKind.key_goto] = 'goto' - s[TokenKind.key_const] = 'const' - s[TokenKind.key_mut] = 'mut' - s[TokenKind.key_type] = 'type' - s[TokenKind.key_for] = 'for' - s[TokenKind.key_switch] = 'switch' - s[TokenKind.key_fn] = 'fn' - s[TokenKind.key_true] = 'true' - s[TokenKind.key_false] = 'false' - s[TokenKind.key_continue] = 'continue' - s[TokenKind.key_break] = 'break' - s[TokenKind.key_import] = 'import' - s[TokenKind.key_embed] = 'embed' - s[TokenKind.key_unsafe] = 'unsafe' - s[TokenKind.key_typeof] = 'typeof' - s[TokenKind.key_enum] = 'enum' - s[TokenKind.key_interface] = 'interface' - s[TokenKind.key_pub] = 'pub' - s[TokenKind.key_import_const] = 'import_const' - s[TokenKind.key_in] = 'in' - s[TokenKind.key_atomic] = 'atomic' - s[TokenKind.key_orelse] = 'or' - s[TokenKind.key_global] = '__global' - s[TokenKind.key_union] = 'union' - s[TokenKind.key_static] = 'static' - s[TokenKind.key_as] = 'as' - s[TokenKind.key_defer] = 'defer' - s[TokenKind.key_match] = 'match' - s[TokenKind.key_select] = 'select' - s[TokenKind.key_none] = 'none' - s[TokenKind.key_offsetof] = '__offsetof' - s[TokenKind.key_nameof] = 'nameof' - return s -} - -const ( - NrTokens = 141 - TokenStr = build_token_str() - KEYWORDS = build_keys() -) - -fn key_to_token(key string) TokenKind { - a := TokenKind(KEYWORDS[key]) - return a -} - -fn is_key(key string) bool { - return int(key_to_token(key)) > 0 -} - -pub fn (t TokenKind) str() string { - return TokenStr[int(t)] -} - -fn (t TokenKind) is_decl() bool { - return t in [.key_enum, .key_interface, .key_fn, .key_struct, .key_type, .key_const, .key_import_const, .key_pub, .eof] -} - -const ( - AssignTokens = [TokenKind.assign, .plus_assign, .minus_assign, .mult_assign, .div_assign, .xor_assign, .mod_assign, .or_assign, .and_assign, .righ_shift_assign, .left_shift_assign] -) - -fn (t TokenKind) is_assign() bool { - return t in AssignTokens -} - -fn (t []TokenKind) contains(val TokenKind) bool { - for tt in t { - if tt == val { - return true - } - } - return false -} - -pub fn (t Token) str() string { - if t.tok == .number { - return t.lit - } - if t.tok == .chartoken { - return '`$t.lit`' - } - if t.tok == .string { - return "'$t.lit'" - } - if t.tok == .eof { - return '.EOF' - } - if t.tok < .plus { - return t.lit // string, number etc - } - return t.tok.str() -} - -pub fn (t Token) detailed_str() string { - return 'Token{ .line:${t.line_nr:4d}, .pos:${t.pos:5d}, .tok: ${t.tok:3d} } = $t ' -} - diff --git a/vlib/compiler/vfmt.v b/vlib/compiler/vfmt.v deleted file mode 100644 index 91d3bced6e..0000000000 --- a/vlib/compiler/vfmt.v +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. - -module compiler - -import strings -import os - -[if vfmt] -fn (scanner mut Scanner) fgen(s_ string) { - mut s := s_ - if s != ' ' { - //s = s.trim_space() - } - if scanner.fmt_line_empty { - s = strings.repeat(`\t`, scanner.fmt_indent) + s.trim_left(' ') - } - scanner.fmt_lines << s - //scanner.fmt_out << s - //scanner.fmt_out.write(s) - scanner.fmt_line_empty = false -} - -[if vfmt] -fn (scanner mut Scanner) fgenln(s_ string) { - mut s := s_.trim_right(' ') - if scanner.fmt_line_empty && scanner.fmt_indent > 0 { - s = strings.repeat(`\t`, scanner.fmt_indent) + s - } - scanner.fmt_lines << s - //println('s="$s"') - //scanner.fmt_lines << '//!' - scanner.fmt_lines << '\n' - //scanner.fmt_out.writeln(s) - scanner.fmt_line_empty = true -} - -[if vfmt] -fn (p mut Parser) fgen(s string) { - if p.pass != .main { - return - } - p.scanner.fgen(s) -} - -[if vfmt] -fn (p mut Parser) fspace() { - if p.first_pass() { - return - } - p.fgen(' ') -} - -[if vfmt] -fn (p mut Parser) fspace_or_newline() { - if p.first_pass() { - return - } - if p.token_idx >= 2 && p.tokens[p.token_idx-1].line_nr != - p.tokens[p.token_idx-2].line_nr { - p.fgen_nl() - } else { - p.fgen(' ') - } -} - - -[if vfmt] -fn (p mut Parser) fgenln(s string) { - if p.pass != .main { - return - } - p.scanner.fgenln(s) -} - -[if vfmt] -fn (p mut Parser) fgen_nl() { - if p.pass != .main { - return - } - - //println(p.tok) - // Don't insert a newline after a comment - /* - if p.token_idx>0 && p.tokens[p.token_idx-1].tok == .line_comment && - p.tokens[p.token_idx].tok != .line_comment { - p.scanner.fgenln('notin') - return - } - */ - - ///if p.token_idx > 0 && p.token_idx < p.tokens.len && - // Previous token is a comment, and NL has already been generated? - // Don't generate a second NL. - if p.scanner.fmt_lines.len > 0 && p.scanner.fmt_lines.last() == '\n' && - p.token_idx > 2 && - p.tokens[p.token_idx-2].tok == .line_comment - { - //if p.fileis('parser.v') { - //println(p.scanner.line_nr.str() + ' ' +p.tokens[p.token_idx-2].str()) - //} - return - } - - p.scanner.fgen_nl() -} - -[if vfmt] -fn (scanner mut Scanner) fgen_nl() { - //scanner.fmt_lines << ' fgen_nl' - //scanner.fmt_lines << '//fgen_nl\n' - scanner.fmt_lines << '\n' - //scanner.fmt_out.writeln('') - scanner.fmt_line_empty = true -} - -/* -fn (p mut Parser) peek() TokenKind { - for { - p.cgen.line = p.scanner.line_nr + 1 - tok := p.scanner.peek() - if tok != .nl { - return tok - } - } - return .eof // TODO can never get here - v doesn't know that -} -*/ - -[if vfmt] -fn (p mut Parser) fmt_inc() { - if p.pass != .main { - return - } - p.scanner.fmt_indent++ -} - -[if vfmt] -fn (p mut Parser) fmt_dec() { - if p.pass != .main { - return - } - p.scanner.fmt_indent-- -} - -[if vfmt] -fn (s mut Scanner) init_fmt() { - // Right now we can't do `$if vfmt {`, so I'm using - // a conditional function init_fmt to set this flag. - // This function will only be called if `-d vfmt` is passed. - s.is_fmt = true -} - -[if vfmt] -fn (p mut Parser) fnext() { - //if p.tok == .eof { - //println('eof ret') - //return - //} - if p.tok == .rcbr && !p.inside_if_expr && p.prev_tok != .lcbr { - p.fmt_dec() - } - s := p.strtok() - if p.tok != .eof { - p.fgen(s) - } - // vfmt: increase indentation on `{` unless it's `{}` - inc_indent := false - if p.tok == .lcbr && !p.inside_if_expr && p.peek() != .rcbr { - p.fgen_nl() - p.fmt_inc() - } - if p.token_idx >= p.tokens.len { - return - } - // Skip comments and add them to vfmt output - if p.tokens[p.token_idx].tok in [.line_comment, .mline_comment] { - // Newline before the comment and after consts and closing } - if p.inside_const { - //p.fgen_nl() - //p.fgen_nl() - } - //is_rcbr := p.tok == .rcbr - for p.token_idx < p.tokens.len - 1 { - i := p.token_idx - tok := p.tokens[p.token_idx].tok - if tok != .line_comment && tok != .mline_comment { - break - } - comment_token := p.tokens[i] - next := p.tokens[i+1] - comment_on_new_line := i == 0 || - comment_token.line_nr > p.tokens[i-1].line_nr - //prev_token := p.tokens[p.token_idx - 1] - comment := comment_token.lit - // Newline before the comment, but not between two // comments, - // and not right after `{`, there's already a newline there - if i > 0 && ((p.tokens[i-1].tok != .line_comment && - p.tokens[i-1].tok != .lcbr && - comment_token.line_nr > p.tokens[i-1].line_nr) || - p.tokens[i-1].tok == .hash) { // TODO not sure why this is needed, newline wasn't added after a hash - p.fgen_nl() - } - if i > 0 && p.tokens[i-1].tok == .rcbr && p.scanner.fmt_indent == 0 { - p.fgen_nl() - } - if tok == .line_comment { - if !comment_on_new_line { //prev_token.line_nr < comment_token.line_nr { - p.fgen(' ') - } - p.fgen('// ' + comment) - /* - if false && i > 0 { - p.fgen( -'pln=${p.tokens[i-1].line_nr} ${comment_token.str()} ' + -'line_nr=$comment_token.line_nr next=${next.str()} next_line_nr=$next.line_nr') -} -*/ - - } else { - // /**/ comment - p.fgen(comment) - } - //if next.tok == .line_comment && comment_token.line_nr < next.line_nr { - if comment_token.line_nr < next.line_nr { - //p.fgenln('nextcm') - p.fgen_nl() - } - p.token_idx++ - } - - if inc_indent { - p.fgen_nl() - } - } -} - -[if vfmt] -fn (p mut Parser) fremove_last() { - if p.scanner.fmt_lines.len > 0 { - p.scanner.fmt_lines[p.scanner.fmt_lines.len-1] = '' - } -} - -[if vfmt] -fn (p &Parser) gen_fmt() { - if p.pass != .main { - return - } - //println('gen fmt name=$p.file_name path=$p.file_path') - if p.file_name == '' { - return - } - is_all := p.v.v_fmt_all - vfmt_file := p.v.v_fmt_file - if p.file_path != vfmt_file && !is_all { - // skip everything except the last file (given by the CLI argument) - return - } - //s := p.scanner.fmt_out.str().replace('\n\n\n', '\n').trim_space() - //s := p.scanner.fmt_out.str().trim_space() - //p.scanner.fgenln('// nice') - mut s := p.scanner.fmt_lines.join('') -/*.replace_each([ - '\n\n\n\n', '\n\n', - ' \n', '\n', - ') or{', ') or {', - ]) - */ - //.replace('\n\n\n\n', '\n\n') - - s = s.replace(' \n', '\n') - s = s.replace(') or {', ') or {') - s = s.replace(') or{', ') or {') - s = s.replace(')or{', ') or {') - s = s.replace('or{', 'or {') - s = s.replace('}}\n', '}\n\t}\n') - - if s == '' { - return - } - //files := ['get_type.v'] - if p.file_path.contains('compiler/vfmt.v') {return} - //if !(p.file_name in files) { return } - if is_all { - if p.file_path.len > 0 { - path := write_formatted_source( p.file_name, s ) - os.cp( path, p.file_path ) or { panic(err) } - eprintln('Written fmt file to: $p.file_path') - } - } - if p.file_path == vfmt_file { - res_path := write_formatted_source( p.file_name, s ) - mut vv := p.v - vv.v_fmt_file_result = res_path - } -} - -fn write_formatted_source(file_name string, s string) string { - path := os.temp_dir() + '/' + file_name - mut out := os.create(path) or { - verror('failed to create file $path') - return '' - } - //eprintln('replacing ${p.file_path} ...\n') - out.writeln(s.trim_space())//p.scanner.fmt_out.str().trim_space()) - out.close() - return path -} diff --git a/vlib/compiler/vtmp.v b/vlib/compiler/vtmp.v deleted file mode 100644 index e914b5b947..0000000000 --- a/vlib/compiler/vtmp.v +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. -// Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. -module compiler - -import os - -fn get_vtmp_folder() string { - vtmp := os.join_path(os.temp_dir(), 'v') - if !os.is_dir(vtmp) { - os.mkdir(vtmp) or { - panic(err) - } - } - return vtmp -} - -fn get_vtmp_filename(base_file_name string, postfix string) string { - vtmp := get_vtmp_folder() - return os.real_path(os.join_path(vtmp, os.file_name(os.real_path(base_file_name)) + postfix)) -} diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index cdb80e9edc..73afeee5ee 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -384,6 +384,10 @@ fn (g mut Gen) stmt(node ast.Stmt) { styp := g.typ(it.typ) g.definitions.writeln('$styp $it.name; // global') } + ast.GoStmt { + g.writeln('// go') + g.expr(it.expr) + } ast.GotoLabel { g.writeln('$it.name:') } @@ -2534,6 +2538,19 @@ fn comp_if_to_ifdef(name string) string { } 'no_bounds_checking' { return 'NO_BOUNDS_CHECK' + } + 'x64' { + return 'TARGET_IS_64BIT' + } + 'x32' { + return 'TARGET_IS_32BIT' + } + 'little_endian' { + return 'TARGET_ORDER_IS_LITTLE' + + } + 'big_endian' { + return 'TARGET_ORDER_IS_BIG' } else { verror('bad os ifdef name "$name"')