blob: 591470e9534c58840e0b5c2d292396918656ac20 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package codegen implements utilities for VDL code generators. Code
// generators for specific languages live in sub-directories.
package codegen
import (
"path"
"sort"
"strconv"
"v.io/v23/vdl"
"v.io/x/lib/set"
"v.io/x/ref/lib/vdl/compile"
)
// TODO(toddw): Remove this file, after all code generators have been updated to
// compute their own import dependencies.
// Import represents a single package import.
type Import struct {
Name string // Name of the import; may be empty.
Path string // Path of the imported package; e.g. "v.io/x/ref/lib/vdl/testdata/arith"
// Local name that refers to the imported package; either the non-empty import
// name, or the name of the imported package.
Local string
}
// Imports is a collection of package imports.
// REQUIRED: The imports must be sorted by path.
type Imports []Import
// LookupLocal returns the local name that identifies the given pkgPath.
func (x Imports) LookupLocal(pkgPath string) string {
ix := sort.Search(len(x), func(i int) bool { return x[i].Path >= pkgPath })
if ix < len(x) && x[ix].Path == pkgPath {
return x[ix].Local
}
return ""
}
// Each import must end up with a unique local name - when we see a collision we
// simply add a "_N" suffix where N starts at 2 and increments.
func uniqueImport(pkgName, pkgPath string, seen map[string]bool) Import {
name := ""
iter := 1
for {
local := pkgName
if iter > 1 {
local += "_" + strconv.Itoa(iter)
name = local
}
if !seen[local] {
// Found a unique local name - return the import.
seen[local] = true
return Import{name, pkgPath, local}
}
iter++
}
}
type pkgSorter []*compile.Package
func (s pkgSorter) Len() int { return len(s) }
func (s pkgSorter) Less(i, j int) bool { return s[i].Path < s[j].Path }
func (s pkgSorter) Swap(i, j int) { s[j], s[i] = s[i], s[j] }
// ImportsForFiles returns the imports required for the given files.
func ImportsForFiles(files ...*compile.File) Imports {
seenPath := make(map[string]bool)
pkgs := pkgSorter{}
for _, f := range files {
for _, dep := range f.PackageDeps {
if seenPath[dep.Path] {
continue
}
seenPath[dep.Path] = true
pkgs = append(pkgs, dep)
}
}
sort.Sort(pkgs)
var ret Imports
seenName := make(map[string]bool)
for _, dep := range pkgs {
ret = append(ret, uniqueImport(dep.Name, dep.Path, seenName))
}
return ret
}
// ImportsForValue returns the imports required to represent the given value v,
// from within the given pkgPath. It requires that type names used in
// v are of the form "PKGPATH.NAME".
func ImportsForValue(v *vdl.Value, pkgPath string) Imports {
deps := pkgDeps{}
deps.MergeValue(v)
var ret Imports
seen := make(map[string]bool)
for _, p := range deps.SortedPkgPaths() {
if p != pkgPath {
ret = append(ret, uniqueImport(path.Base(p), p, seen))
}
}
return ret
}
// pkgDeps maintains a set of package path dependencies.
type pkgDeps map[string]bool
func (deps pkgDeps) insertIdent(ident string) {
if pkgPath, _ := vdl.SplitIdent(ident); pkgPath != "" {
deps[pkgPath] = true
}
}
// MergeValue merges the package paths required to represent v into deps.
func (deps pkgDeps) MergeValue(v *vdl.Value) {
deps.insertIdent(v.Type().Name())
switch v.Kind() {
case vdl.Any, vdl.Union, vdl.Optional:
elem := v.Elem()
if elem != nil {
deps.MergeValue(elem)
}
case vdl.Array, vdl.List:
deps.insertIdent(v.Type().Elem().Name())
case vdl.Set:
deps.insertIdent(v.Type().Key().Name())
case vdl.Map:
deps.insertIdent(v.Type().Key().Name())
deps.insertIdent(v.Type().Elem().Name())
case vdl.Struct:
for fx := 0; fx < v.Type().NumField(); fx++ {
deps.insertIdent(v.Type().Field(fx).Type.Name())
}
}
}
// SortedPkgPaths deps as a sorted slice.
func (deps pkgDeps) SortedPkgPaths() []string {
ret := set.StringBool.ToSlice(deps)
sort.Strings(ret)
return ret
}