modules: cyclic import detection

pull/1195/head
joe-conigliaro 2019-07-22 01:53:35 +10:00 committed by Alexander Medvednikov
parent 23c5f88f3e
commit 135f200ea2
4 changed files with 180 additions and 39 deletions

View File

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

141
compiler/modules.v 100644
View File

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

View File

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

View File

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