package modules
import (
"fmt"
"os"
"path/filepath"
"github.com/marzeq/quokka/parser"
"github.com/marzeq/quokka/tokeniser"
)
type FileNode struct {
Path string
Imports []*FileNode
}
type DepGraph struct {
Files map[string]*FileNode
ASTs map[string]*parser.RootNode
}
func NewDepGraph(path string, rn *parser.RootNode) (*DepGraph, error) {
dg := &DepGraph{
Files: make(map[string]*FileNode),
ASTs: make(map[string]*parser.RootNode),
}
err := dg.Construct(path, rn)
if err != nil {
return nil, err
}
return dg, nil
}
var moduleSearchPaths = []string{
".",
"/usr/share/quokka/lib",
"/usr/local/share/quokka/lib",
filepath.Join(os.Getenv("HOME"), ".local/share/quokka/lib"),
}
func (g *DepGraph) Construct(path string, rn *parser.RootNode) error {
path, err := filepath.Abs(path)
if err != nil {
return err
}
if _, ok := g.ASTs[path]; ok {
return nil
}
node := &FileNode{
Path: path,
Imports: []*FileNode{},
}
g.Files[path] = node
g.ASTs[path] = rn
modules := getImports(rn)
for _, moduleName := range modules {
files, err := findFilesForModule(moduleName)
if err != nil {
return err
}
modules := getImports(rn)
for _, moduleName := range modules {
files, err := findFilesForModule(moduleName)
if err != nil {
return err
}
if len(files) == 0 {
return fmt.Errorf("module not found: %s", moduleName)
}
for _, f := range files {
childNode, ok := g.Files[f]
if !ok || childNode == nil {
continue
}
node.Imports = append(node.Imports, childNode)
}
}
for _, f := range files {
t, err := tokeniser.NewTokeniserFromFile(f)
if err != nil {
return err
}
toks, err := t.Tokenise()
if err != nil {
return err
}
p := parser.NewParser(toks)
ast, err := p.Parse()
if err != nil {
return err
}
firstModule := ""
if len(ast.Body) > 0 {
if mn, ok := ast.Body[0].(*parser.ModuleNode); ok {
firstModule = mn.Name
}
}
if firstModule != moduleName {
continue
}
if err := g.Construct(f, ast); err != nil {
return err
}
node.Imports = append(node.Imports, g.Files[f])
}
}
return nil
}
func getImports(rn *parser.RootNode) []string {
modules := []string{}
for _, n := range rn.Body {
if imp, ok := n.(*parser.ImportNode); ok {
modules = append(modules, imp.Modules...)
}
}
return modules
}
func findFilesForModule(moduleName string) ([]string, error) {
var result []string
seen := map[string]struct{}{}
for _, dir := range moduleSearchPaths {
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return nil
}
if _, ok := seen[path]; ok {
return nil
}
if filepath.Ext(path) != ".qk" {
return nil
}
t, err := tokeniser.NewTokeniserFromFile(path)
if err != nil {
return nil
}
toks, err := t.Tokenise()
if err != nil {
return nil
}
p := parser.NewParser(toks)
ast, err := p.Parse()
if err != nil || len(ast.Body) == 0 {
return nil
}
if mn, ok := ast.Body[0].(*parser.ModuleNode); ok && mn.Name == moduleName {
result = append(result, path)
seen[path] = struct{}{}
}
return nil
})
if err != nil {
return nil, err
}
}
return result, nil
}
func (g *DepGraph) TopoSort() ([]string, error) {
visited := map[string]bool{}
temp := map[string]bool{}
result := []string{}
var visit func(n *FileNode) error
visit = func(n *FileNode) error {
if n == nil {
return nil
}
if temp[n.Path] {
return fmt.Errorf("cycle detected at file: %s", n.Path)
}
if visited[n.Path] {
return nil
}
temp[n.Path] = true
for _, child := range n.Imports {
if err := visit(child); err != nil {
return err
}
}
temp[n.Path] = false
visited[n.Path] = true
result = append(result, n.Path)
return nil
}
for _, n := range g.Files {
if !visited[n.Path] {
if err := visit(n); err != nil {
return nil, err
}
}
}
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
result[i], result[j] = result[j], result[i]
}
return result, nil
}