module builder import os import v.token import v.pref import v.errors import v.util import v.ast import v.vmod import v.checker import v.transformer import v.parser import v.markused import v.depgraph import v.callgraph import v.dotgraph pub struct Builder { pub: compiled_dir string // contains os.real_path() of the dir of the final file beeing compiled, or the dir itself when doing `v .` module_path string pub mut: checker &checker.Checker transformer &transformer.Transformer out_name_c string out_name_js string stats_lines int // size of backend generated source code in lines stats_bytes int // size of backend generated source code in bytes nr_errors int // accumulated error count of scanner, parser, checker, and builder nr_warnings int // accumulated warning count of scanner, parser, checker, and builder nr_notices int // accumulated notice count of scanner, parser, checker, and builder pref &pref.Preferences module_search_paths []string parsed_files []&ast.File cached_msvc MsvcResult table &ast.Table ccoptions CcompilerOptions // // Note: changes in mod `builtin` force invalidation of every other .v file mod_invalidates_paths map[string][]string // changes in mod `os`, invalidate only .v files, that do `import os` mod_invalidates_mods map[string][]string // changes in mod `os`, force invalidation of mods, that do `import os` path_invalidates_mods map[string][]string // changes in a .v file from `os`, invalidates `os` } pub fn new_builder(pref &pref.Preferences) Builder { rdir := os.real_path(pref.path) compiled_dir := if os.is_dir(rdir) { rdir } else { os.dir(rdir) } mut table := ast.new_table() table.is_fmt = false if pref.use_color == .always { util.emanager.set_support_color(true) } if pref.use_color == .never { util.emanager.set_support_color(false) } msvc := find_msvc(pref.m64) or { if pref.ccompiler == 'msvc' { // verror('Cannot find MSVC on this OS') } MsvcResult{ valid: false } } util.timing_set_should_print(pref.show_timings || pref.is_verbose) if pref.show_callgraph || pref.show_depgraph { dotgraph.start_digraph() } return Builder{ pref: pref table: table checker: checker.new_checker(table, pref) transformer: transformer.new_transformer_with_table(table, pref) compiled_dir: compiled_dir cached_msvc: msvc } } pub fn (mut b Builder) front_stages(v_files []string) ? { mut timers := util.get_timers() util.timing_start('PARSE') util.timing_start('Builder.front_stages.parse_files') b.parsed_files = parser.parse_files(v_files, b.table, b.pref) timers.show('Builder.front_stages.parse_files') b.parse_imports() timers.show('SCAN') timers.show('PARSE') timers.show_if_exists('PARSE stmt') if b.pref.only_check_syntax { return error_with_code('stop_after_parser', 9999) } } pub fn (mut b Builder) middle_stages() ? { util.timing_start('CHECK') util.timing_start('Checker.generic_insts_to_concrete') b.table.generic_insts_to_concrete() util.timing_measure('Checker.generic_insts_to_concrete') b.checker.check_files(b.parsed_files) util.timing_measure('CHECK') b.print_warnings_and_errors() if b.checker.should_abort { return error('too many errors/warnings/notices') } if b.pref.check_only { return error_with_code('stop_after_checker', 9999) } util.timing_start('TRANSFORM') b.transformer.transform_files(b.parsed_files) util.timing_measure('TRANSFORM') // b.table.complete_interface_check() if b.pref.skip_unused { markused.mark_used(mut b.table, b.pref, b.parsed_files) } if b.pref.show_callgraph { callgraph.show(mut b.table, b.pref, b.parsed_files) } } pub fn (mut b Builder) front_and_middle_stages(v_files []string) ? { b.front_stages(v_files) ? b.middle_stages() ? } // parse all deps from already parsed files pub fn (mut b Builder) parse_imports() { util.timing_start(@METHOD) defer { util.timing_measure(@METHOD) } mut done_imports := []string{} if b.pref.is_vsh { done_imports << 'os' } // TODO (joe): decide if this is correct solution. // in the case of building a module, the actual module files // are passed via cmd line, so they have already been parsed // by this stage. note that if one files from a module was // parsed (but not all of them), then this will cause a problem. // we could add a list of parsed files instead, but I think // there is a better solution all around, I will revisit this. // NOTE: there is a very similar occurance with the way // internal module test's work, and this was the reason there // were issues with duplicate declarations, so we should sort // that out in a similar way. for file in b.parsed_files { if file.mod.name != 'main' && file.mod.name !in done_imports { done_imports << file.mod.name } } // Note: b.parsed_files is appended in the loop, // so we can not use the shorter `for in` form. for i := 0; i < b.parsed_files.len; i++ { ast_file := b.parsed_files[i] b.path_invalidates_mods[ast_file.path] << ast_file.mod.name if ast_file.mod.name != 'builtin' { b.mod_invalidates_paths['builtin'] << ast_file.path b.mod_invalidates_mods['builtin'] << ast_file.mod.name } for imp in ast_file.imports { mod := imp.mod b.mod_invalidates_paths[mod] << ast_file.path b.mod_invalidates_mods[mod] << ast_file.mod.name if mod == 'builtin' { b.parsed_files[i].errors << b.error_with_pos('cannot import module "builtin"', ast_file.path, imp.pos) break } if mod in done_imports { continue } import_path := b.find_module_path(mod, ast_file.path) or { // v.parsers[i].error_with_token_index('cannot import module "$mod" (not found)', v.parsers[i].import_ast.get_import_tok_idx(mod)) // break b.parsed_files[i].errors << b.error_with_pos('cannot import module "$mod" (not found)', ast_file.path, imp.pos) break } v_files := b.v_files_from_dir(import_path) if v_files.len == 0 { // v.parsers[i].error_with_token_index('cannot import module "$mod" (no .v files in "$import_path")', v.parsers[i].import_ast.get_import_tok_idx(mod)) b.parsed_files[i].errors << b.error_with_pos('cannot import module "$mod" (no .v files in "$import_path")', ast_file.path, imp.pos) continue } // eprintln('>> ast_file.path: $ast_file.path , done: $done_imports, `import $mod` => $v_files') // Add all imports referenced by these libs parsed_files := parser.parse_files(v_files, b.table, b.pref) for file in parsed_files { mut name := file.mod.name if name == '' { name = file.mod.short_name } sname := name.all_after_last('.') smod := mod.all_after_last('.') if sname != smod { msg := 'bad module definition: $ast_file.path imports module "$mod" but $file.path is defined as module `$name`' b.parsed_files[i].errors << b.error_with_pos(msg, ast_file.path, imp.pos) } } b.parsed_files << parsed_files done_imports << mod } } b.resolve_deps() if b.pref.print_v_files { for p in b.parsed_files { println(p.path) } exit(0) } b.rebuild_modules() } pub fn (mut b Builder) resolve_deps() { util.timing_start(@METHOD) defer { util.timing_measure(@METHOD) } graph := b.import_graph() deps_resolved := graph.resolve() if b.pref.is_verbose { eprintln('------ resolved dependencies graph: ------') eprintln(deps_resolved.display()) eprintln('------------------------------------------') } if b.pref.show_depgraph { depgraph.show(deps_resolved, b.pref.path) } cycles := deps_resolved.display_cycles() if cycles.len > 1 { verror('error: import cycle detected between the following modules: \n' + cycles) } mut mods := []string{} for node in deps_resolved.nodes { mods << node.name } if b.pref.is_verbose { eprintln('------ imported modules: ------') eprintln(mods.str()) eprintln('-------------------------------') } mut reordered_parsed_files := []&ast.File{} for m in mods { for pf in b.parsed_files { if m == pf.mod.name { reordered_parsed_files << pf // eprintln('pf.mod.name: $pf.mod.name | pf.path: $pf.path') } } } b.table.modules = mods b.parsed_files = reordered_parsed_files } // graph of all imported modules pub fn (b &Builder) import_graph() &depgraph.DepGraph { builtins := util.builtin_module_parts.clone() mut graph := depgraph.new_dep_graph() for p in b.parsed_files { // eprintln('p.path: $p.path') mut deps := []string{} if p.mod.name !in builtins { deps << 'builtin' if b.pref.backend == .c { // TODO JavaScript backend doesn't handle os for now if b.pref.is_vsh && p.mod.name !in ['os', 'dl'] { deps << 'os' } } } for m in p.imports { if m.mod == p.mod.name { continue } deps << m.mod } graph.add(p.mod.name, deps) } $if trace_import_graph ? { eprintln(graph.display()) } return graph } pub fn (b Builder) v_files_from_dir(dir string) []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 b.pref.is_verbose { println('v_files_from_dir ("$dir")') } return b.pref.should_compile_filtered_files(dir, files) } pub fn (b Builder) log(s string) { if b.pref.is_verbose { println(s) } } pub fn (b Builder) info(s string) { if b.pref.is_verbose { println(s) } } [inline] pub fn module_path(mod string) string { // submodule support return mod.replace('.', os.path_separator) } // TODO: try to merge this & util.module functions to create a // reliable multi use function. see comments in util/module.v pub fn (b &Builder) find_module_path(mod string, fpath string) ?string { // support @VROOT/v.mod relative paths: mut mcache := vmod.get_cache() vmod_file_location := mcache.get_by_file(fpath) mod_path := module_path(mod) mut module_lookup_paths := []string{} if vmod_file_location.vmod_file.len != 0 && vmod_file_location.vmod_folder !in b.module_search_paths { module_lookup_paths << vmod_file_location.vmod_folder } module_lookup_paths << b.module_search_paths module_lookup_paths << os.getwd() // go up through parents looking for modules a folder. // we need a proper solution that works most of the time. look at vdoc.get_parent_mod if fpath.contains(os.path_separator + 'modules' + os.path_separator) { parts := fpath.split(os.path_separator) for i := parts.len - 2; i >= 0; i-- { if parts[i] == 'modules' { module_lookup_paths << parts[0..i + 1].join(os.path_separator) break } } } for search_path in module_lookup_paths { try_path := os.join_path(search_path, mod_path) if b.pref.is_verbose { println(' >> trying to find $mod in $try_path ..') } if os.is_dir(try_path) { if b.pref.is_verbose { println(' << found $try_path .') } return try_path } } // look up through parents path_parts := fpath.split(os.path_separator) for i := path_parts.len - 2; i > 0; i-- { p1 := path_parts[0..i].join(os.path_separator) try_path := os.join_path(p1, mod_path) if b.pref.is_verbose { println(' >> trying to find $mod in $try_path ..') } if os.is_dir(try_path) { return try_path } } smodule_lookup_paths := module_lookup_paths.join(', ') return error('module "$mod" not found in:\n$smodule_lookup_paths') } pub fn (b &Builder) show_total_warns_and_errors_stats() { if b.nr_errors == 0 && b.nr_warnings == 0 && b.nr_notices == 0 { return } if b.pref.is_stats { mut nr_errors := b.checker.errors.len mut nr_warnings := b.checker.warnings.len mut nr_notices := b.checker.notices.len if b.pref.check_only { nr_errors = b.nr_errors nr_warnings = b.nr_warnings nr_notices = b.nr_notices } estring := util.bold(nr_errors.str()) wstring := util.bold(nr_warnings.str()) nstring := util.bold(nr_notices.str()) if b.pref.check_only { println('summary: $estring V errors, $wstring V warnings, $nstring V notices') } else { println('checker summary: $estring V errors, $wstring V warnings, $nstring V notices') } } } pub fn (mut b Builder) print_warnings_and_errors() { defer { b.show_total_warns_and_errors_stats() } for file in b.parsed_files { b.nr_errors += file.errors.len b.nr_warnings += file.warnings.len b.nr_notices += file.notices.len } if b.pref.output_mode == .silent { if b.nr_errors > 0 { exit(1) } return } if b.pref.check_only { for file in b.parsed_files { if !b.pref.skip_warnings { for err in file.notices { kind := if b.pref.is_verbose { '$err.reporter notice #$b.nr_notices:' } else { 'notice:' } ferror := util.formatted_error(kind, err.message, err.file_path, err.pos) eprintln(ferror) if err.details.len > 0 { eprintln('Details: $err.details') } } } } for file in b.parsed_files { for err in file.errors { kind := if b.pref.is_verbose { '$err.reporter error #$b.nr_errors:' } else { 'error:' } ferror := util.formatted_error(kind, err.message, err.file_path, err.pos) eprintln(ferror) if err.details.len > 0 { eprintln('Details: $err.details') } } } for file in b.parsed_files { if !b.pref.skip_warnings { for err in file.warnings { kind := if b.pref.is_verbose { '$err.reporter warning #$b.nr_warnings:' } else { 'warning:' } ferror := util.formatted_error(kind, err.message, err.file_path, err.pos) eprintln(ferror) if err.details.len > 0 { eprintln('Details: $err.details') } } } } b.show_total_warns_and_errors_stats() if b.nr_errors > 0 { exit(1) } exit(0) } if b.pref.is_verbose && b.checker.nr_warnings > 1 { println('$b.checker.nr_warnings warnings') } if b.pref.is_verbose && b.checker.nr_notices > 1 { println('$b.checker.nr_notices notices') } if b.checker.nr_notices > 0 && !b.pref.skip_warnings { for err in b.checker.notices { kind := if b.pref.is_verbose { '$err.reporter notice #$b.checker.nr_notices:' } else { 'notice:' } ferror := util.formatted_error(kind, err.message, err.file_path, err.pos) eprintln(ferror) if err.details.len > 0 { eprintln('Details: $err.details') } } } if b.checker.nr_warnings > 0 && !b.pref.skip_warnings { for err in b.checker.warnings { kind := if b.pref.is_verbose { '$err.reporter warning #$b.checker.nr_warnings:' } else { 'warning:' } ferror := util.formatted_error(kind, err.message, err.file_path, err.pos) eprintln(ferror) if err.details.len > 0 { eprintln('Details: $err.details') } } } // if b.pref.is_verbose && b.checker.nr_errors > 1 { println('$b.checker.nr_errors errors') } if b.checker.nr_errors > 0 { for err in b.checker.errors { kind := if b.pref.is_verbose { '$err.reporter error #$b.checker.nr_errors:' } else { 'error:' } ferror := util.formatted_error(kind, err.message, err.file_path, err.pos) eprintln(ferror) if err.details.len > 0 { eprintln('Details: $err.details') } } b.show_total_warns_and_errors_stats() exit(1) } if b.table.redefined_fns.len > 0 { mut total_conflicts := 0 for fn_name in b.table.redefined_fns { // Find where this function was already declared mut redefines := []FunctionRedefinition{} mut redefine_conflicts := map[string]int{} for file in b.parsed_files { for stmt in file.stmts { if stmt is ast.FnDecl { if stmt.name == fn_name { fheader := stmt.stringify(b.table, 'main', map[string]string{}) redefines << FunctionRedefinition{ fpath: file.path fline: stmt.pos.line_nr f: stmt fheader: fheader } redefine_conflicts[fheader]++ } } } } if redefines.len > 0 { eprintln('redefinition of function `$fn_name`') for redefine in redefines { eprintln(util.formatted_error('conflicting declaration:', redefine.fheader, redefine.fpath, redefine.f.pos)) } total_conflicts++ } } if total_conflicts > 0 { b.show_total_warns_and_errors_stats() exit(1) } } } struct FunctionRedefinition { fpath string fline int fheader string f ast.FnDecl } pub fn (b &Builder) error_with_pos(s string, fpath string, pos token.Pos) errors.Error { if !b.pref.check_only { ferror := util.formatted_error('builder error:', s, fpath, pos) eprintln(ferror) exit(1) } return errors.Error{ file_path: fpath pos: pos reporter: .builder message: s } } [noreturn] pub fn verror(s string) { util.verror('builder error', s) }