diff --git a/compiler/main.v b/compiler/main.v index 831aaa0e11..1181403824 100644 --- a/compiler/main.v +++ b/compiler/main.v @@ -751,16 +751,14 @@ fn (v mut V) add_user_v_files() { v.log('user_files:') println(user_files) } - // To store the import table for user imports - mut user_imports := []FileImportTable + // import tables for user/lib files + mut file_imports := []FileImportTable // Parse user imports for file in user_files { mut p := v.new_parser(file, Pass.imports) p.parse() - user_imports << *p.import_table + file_imports << *p.import_table } - // To store the import table for lib imports - mut lib_imports := []FileImportTable // Parse lib imports if v.pref.build_mode == .default_mode { for i := 0; i < v.table.imports.len; i++ { @@ -770,7 +768,7 @@ fn (v mut V) add_user_v_files() { for file in vfiles { mut p := v.new_parser(file, Pass.imports) p.parse() - lib_imports << *p.import_table + file_imports << *p.import_table } } } @@ -789,7 +787,7 @@ fn (v mut V) add_user_v_files() { for file in vfiles { mut p := v.new_parser(file, Pass.imports) p.parse() - lib_imports << *p.import_table + file_imports << *p.import_table } } } @@ -797,38 +795,38 @@ fn (v mut V) add_user_v_files() { v.log('imports:') println(v.table.imports) } - // this order is important for declaration - mut combined_imports := []FileImportTable - for i := lib_imports.len-1; i>=0; i-- { - combined_imports << lib_imports[i] + // graph deps + mut dep_graph := new_mod_dep_graph() + dep_graph.from_import_tables(file_imports) + deps_resolved := dep_graph.resolve() + if !deps_resolved.acyclic { + deps_resolved.display() + panic('Import cycle detected.') } - for i in user_imports { - combined_imports << i - } - // Only now add all combined lib files - // adding the modules each file imports first - for fit in combined_imports { - for _, mod in fit.imports { - mod_p := v.module_path(mod) - idir := os.getwd() - mut module_path := '$idir/$mod_p' - // If we are in default mode, we don't parse vlib .v files, but header .vh files in - // TmpPath/vlib - // These were generated by vfmt - if v.pref.build_mode == .default_mode || v.pref.build_mode == .build { - module_path = '$ModPath/vlib/$mod_p' - } - if !os.file_exists(module_path) { - module_path = '$v.lang_dir/vlib/$mod_p' - } - vfiles := v.v_files_from_dir(module_path) - for file in vfiles { - if !file in v.files { - v.files << file - } - } - // TODO v.files.append_array(vfiles) + // add imports in correct order + for mod in deps_resolved.imports() { + mod_p := v.module_path(mod) + idir := os.getwd() + mut module_path := '$idir/$mod_p' + // If we are in default mode, we don't parse vlib .v files, but header .vh files in + // TmpPath/vlib + // These were generated by vfmt + if v.pref.build_mode == .default_mode || v.pref.build_mode == .build { + module_path = '$ModPath/vlib/$mod_p' } + if !os.file_exists(module_path) { + module_path = '$v.lang_dir/vlib/$mod_p' + } + vfiles := v.v_files_from_dir(module_path) + for file in vfiles { + if !file in v.files { + v.files << file + } + } + // TODO v.files.append_array(vfiles) + } + // add remaining files (not mods) + for fit in file_imports { if !fit.file_path in v.files { v.files << fit.file_path } diff --git a/compiler/modules.v b/compiler/modules.v new file mode 100644 index 0000000000..8817ad4400 --- /dev/null +++ b/compiler/modules.v @@ -0,0 +1,141 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module main + +struct ModDepGraphNode { + name string + deps []string +} + +struct ModDepGraph { +pub: + mut: + acyclic bool + nodes []ModDepGraphNode +} + +struct DepSet { + mut: + items []string +} + +pub fn(mapset mut DepSet) add(item string) { + mapset.items << item +} + +pub fn(mapset &DepSet) diff(otherset DepSet) DepSet { + mut diff := DepSet{} + for item in mapset.items { + if !item in otherset.items { + diff.items << item + } + } + return diff +} + +pub fn(mapset &DepSet) size() int { + return mapset.items.len +} + +pub fn new_mod_dep_graph() *ModDepGraph { + return &ModDepGraph{ + acyclic: true + } +} + +pub fn(graph mut ModDepGraph) from_import_tables(import_tables []FileImportTable) { + for fit in import_tables { + mut deps := []string + for _, m in fit.imports { + deps << m + } + graph.add(fit.module_name, deps) + } +} + +pub fn(graph mut ModDepGraph) add(mod string, deps []string) { + graph.nodes << ModDepGraphNode{ + name: mod, + deps: deps + } +} + +pub fn(graph &ModDepGraph) resolve() *ModDepGraph { + mut node_names := map[string]ModDepGraphNode{} + mut node_deps := map[string]DepSet{} + + for _, node in graph.nodes { + node_names[node.name] = node + + mut dep_set := DepSet{} + for _, dep in node.deps { + dep_set.add(dep) + } + node_deps[node.name] = dep_set + } + + mut resolved := new_mod_dep_graph() + for node_deps.size != 0 { + mut ready_set := DepSet{} + for name, deps in node_deps { + if deps.size() == 0 { + ready_set.add(name) + } + } + + if ready_set.size() == 0 { + mut g := new_mod_dep_graph() + g.acyclic = false + for name, _ in node_deps { + g.nodes << node_names[name] + } + return g + } + + ready_set.items.len > 0 { + mut new_set := map[string]DepSet{} + for name in ready_set.items { + // node_deps.remove(name) + resolved.nodes << node_names[name] + } + // remove once we have map.remove/delete + for k, d in node_deps { + if !k in ready_set.items { + new_set[k] = d + } + } + node_deps = new_set + } + + for name, deps in node_deps { + node_deps[name] = deps.diff(ready_set) + } + } + + return resolved +} + +pub fn(graph &ModDepGraph) imports() []string { + mut mods := []string + for node in graph.nodes { + if node.name == 'main' { + continue + } + mods << node.name + } + return mods +} + +pub fn(graph &ModDepGraph) last_node() { + return graph.nodes[graph.nodes.len-1] +} + +pub fn(graph &ModDepGraph) display() { + for _, node in graph.nodes { + for _, dep in node.deps { + println(' * $node.name -> $dep') + } + } +} diff --git a/compiler/parser.v b/compiler/parser.v index 36016d7ce3..13f2698364 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -149,6 +149,7 @@ fn (p mut Parser) parse() { // Import pass - the first and the smallest pass that only analyzes imports // fully qualify the module name, eg base64 to encoding.base64 fq_mod := p.table.qualify_module(p.mod, p.file_path) + p.import_table.module_name = fq_mod p.table.register_package(fq_mod) // replace "." with "_dot_" in module name for C variable names p.mod = fq_mod.replace('.', '_dot_') diff --git a/compiler/table.v b/compiler/table.v index 34003c6e21..6256d1d05d 100644 --- a/compiler/table.v +++ b/compiler/table.v @@ -22,8 +22,9 @@ mut: // Holds import information scoped to the parsed file struct FileImportTable { mut: - file_path string - imports map[string]string + module_name string + file_path string + imports map[string]string } enum AccessMod {