| // 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 compile implements the VDL compiler, converting a parse tree into |
| // compiled results. The CompilePackage function is the main entry point. |
| package compile |
| |
| // The job of the compiler is to take parse results as input, and output |
| // compiled results. The concepts between the parser and compiler are very |
| // similar, thus the naming of parse/compile results is also similar. |
| // E.g. parse.File represents a parsed file, while compile.File represents a |
| // compiled file. |
| // |
| // The flow of the compiler is contained in the Compile function below, and |
| // basically defines one concept across all files in the package before moving |
| // onto the next concept. E.g. we define all types in the package before |
| // defining all consts in the package. |
| // |
| // The logic for simple concepts (e.g. imports) is contained directly in this |
| // file, while more complicated concepts (types, consts and interfaces) each get |
| // their own file. |
| |
| import ( |
| "path/filepath" |
| "sort" |
| |
| "v.io/v23/vdl" |
| "v.io/v23/vdlroot/vdltool" |
| "v.io/x/ref/lib/vdl/parse" |
| ) |
| |
| // CompilePackage compiles a list of parse.Files into a Package. Updates env |
| // with the compiled package and returns it on success, or returns nil and |
| // guarantees !env.Errors.IsEmpty(). All imports that the parsed package depend |
| // on must already have been compiled and populated into env. |
| func CompilePackage(pkgpath, genpath string, pfiles []*parse.File, config vdltool.Config, env *Env) *Package { |
| if pkgpath == "" { |
| env.Errors.Errorf("Compile called with empty pkgpath") |
| return nil |
| } |
| if env.pkgs[pkgpath] != nil { |
| env.Errors.Errorf("%q invalid recompile (already exists in env)", pkgpath) |
| return nil |
| } |
| pkg := compile(pkgpath, genpath, pfiles, config, env) |
| if pkg == nil { |
| return nil |
| } |
| if computeDeps(pkg, env); !env.Errors.IsEmpty() { |
| return nil |
| } |
| env.pkgs[pkg.Path] = pkg |
| return pkg |
| } |
| |
| // CompileConfig compiles a parse.Config into a value. Returns the compiled |
| // value on success, or returns nil and guarantees !env.Errors.IsEmpty(). All |
| // imports that the parsed config depend on must already have been compiled and |
| // populated into env. If t is non-nil, the returned value will be of that |
| // type. |
| func CompileConfig(t *vdl.Type, pconfig *parse.Config, env *Env) *vdl.Value { |
| if pconfig == nil || env == nil { |
| env.Errors.Errorf("CompileConfig called with nil config or env") |
| return nil |
| } |
| // Since the concepts are so similar between config files and vdl files, we |
| // just compile it as a single-file vdl package, and compile the exported |
| // config const to retrieve the final exported config value. |
| pfile := &parse.File{ |
| BaseName: filepath.Base(pconfig.FileName), |
| PackageDef: pconfig.ConfigDef, |
| Imports: pconfig.Imports, |
| ConstDefs: pconfig.ConstDefs, |
| } |
| pkgpath := filepath.ToSlash(filepath.Dir(pconfig.FileName)) |
| pkg := compile(pkgpath, pkgpath, []*parse.File{pfile}, vdltool.Config{}, env) |
| if pkg == nil { |
| return nil |
| } |
| config := compileConst("config", t, pconfig.Config, pkg.Files[0], env) |
| // Wait to compute deps after we've compiled the config const expression, |
| // since it might include the only usage of some of the imports. |
| if computeDeps(pkg, env); !env.Errors.IsEmpty() { |
| return nil |
| } |
| return config |
| } |
| |
| // CompileExpr compiles expr into a value. Returns the compiled value on |
| // success, or returns nil and guarantees !env.Errors.IsEmpty(). All imports |
| // that expr depends on must already have been compiled and populated into env. |
| // If t is non-nil, the returned value will be of that type. |
| func CompileExpr(t *vdl.Type, expr parse.ConstExpr, env *Env) *vdl.Value { |
| file := &File{ |
| BaseName: "_expr.vdl", |
| Package: newPackage("_expr", "_expr", "_expr", vdltool.Config{}), |
| imports: make(map[string]*importPath), |
| } |
| // Add imports to the "File" if the are in env and used in the Expression. |
| for _, pkg := range parse.ExtractExprPackagePaths(expr) { |
| if env.pkgs[pkg] != nil { |
| file.imports[pkg] = &importPath{path: pkg} |
| } |
| } |
| return compileConst("expression", t, expr, file, env) |
| } |
| |
| func compile(pkgpath, genpath string, pfiles []*parse.File, config vdltool.Config, env *Env) *Package { |
| if len(pfiles) == 0 { |
| env.Errors.Errorf("%q compile called with no files", pkgpath) |
| return nil |
| } |
| // Initialize each file and put it in pkg. |
| pkgName := parse.InferPackageName(pfiles, env.Errors) |
| if _, err := validIdent(pkgName, reservedNormal); err != nil { |
| env.Errors.Errorf("package %s invalid name (%s)", pkgName, err.Error()) |
| return nil |
| } |
| pkg := newPackage(pkgName, pkgpath, genpath, config) |
| for _, pfile := range pfiles { |
| pkg.Files = append(pkg.Files, &File{ |
| BaseName: pfile.BaseName, |
| PackageDef: NamePos(pfile.PackageDef), |
| Package: pkg, |
| imports: make(map[string]*importPath), |
| }) |
| } |
| // Compile our various structures. The order of these operations matters; |
| // e.g. we must compile types before consts, since consts may use a type |
| // defined in this package. |
| if compileFileDoc(pkg, pfiles, env); !env.Errors.IsEmpty() { |
| return nil |
| } |
| if compileImports(pkg, pfiles, env); !env.Errors.IsEmpty() { |
| return nil |
| } |
| if compileTypeDefs(pkg, pfiles, env); !env.Errors.IsEmpty() { |
| return nil |
| } |
| if compileErrorDefs(pkg, pfiles, env); !env.Errors.IsEmpty() { |
| return nil |
| } |
| if compileConstDefs(pkg, pfiles, env); !env.Errors.IsEmpty() { |
| return nil |
| } |
| if compileInterfaces(pkg, pfiles, env); !env.Errors.IsEmpty() { |
| return nil |
| } |
| return pkg |
| } |
| |
| func compileFileDoc(pkg *Package, pfiles []*parse.File, env *Env) { |
| for index := range pfiles { |
| file, pfile := pkg.Files[index], pfiles[index] |
| if index == 0 { |
| pkg.FileDoc = pfile.Doc |
| } else if pkg.FileDoc != pfile.Doc { |
| // We force all file-doc to be the same, since *.vdl files aren't 1-to-1 |
| // with the generated files in each language, e.g. Java creates one file |
| // per class, while Javascript creates a single file for the entire |
| // package. For the common-case where we use file-doc for copyright |
| // headers, it also prevents the user from accidentally adding copyright |
| // headers to one file but not another, in the same package. |
| env.Errorf(file, parse.Pos{1, 1}, "all files in a package must have the same file doc (the comment on the first line of each *.vdl file that isn't package doc)") |
| } |
| } |
| } |
| |
| func compileImports(pkg *Package, pfiles []*parse.File, env *Env) { |
| for index := range pfiles { |
| file, pfile := pkg.Files[index], pfiles[index] |
| for _, pimp := range pfile.Imports { |
| if dep := env.ResolvePackage(pimp.Path); dep == nil { |
| env.Errorf(file, pimp.Pos, "import path %q not found", pimp.Path) |
| } |
| local := pimp.LocalName() |
| if dup := file.imports[local]; dup != nil { |
| env.Errorf(file, pimp.Pos, "import %s reused (previous at %s)", local, dup.pos) |
| continue |
| } |
| file.imports[local] = &importPath{pimp.Path, pimp.Pos, false} |
| } |
| } |
| } |
| |
| // TODO(toddw): Remove this function and all helpers, after all code generators |
| // have been updated to compute their own dependencies. The only code that will |
| // remain below this point is the loop checking for unused imports. |
| func computeDeps(pkg *Package, env *Env) { |
| // Check for unused user-supplied imports. |
| for _, file := range pkg.Files { |
| for _, imp := range file.imports { |
| if !imp.used { |
| env.Errorf(file, imp.pos, "import path %q unused", imp.path) |
| } |
| } |
| } |
| // Compute type and package dependencies per-file, based on the types and |
| // interfaces that are actually used. We ignore const dependencies, since |
| // we've already evaluated the const expressions. |
| for _, file := range pkg.Files { |
| tdeps := make(map[*vdl.Type]bool) |
| pdeps := make(map[*Package]bool) |
| // TypeDef.Type is always defined in our package; start with sub types. |
| for _, def := range file.TypeDefs { |
| addSubTypeDeps(def.Type, pkg, env, tdeps, pdeps) |
| } |
| // Consts contribute their value types. |
| for _, def := range file.ConstDefs { |
| addValueTypeDeps(def.Value, pkg, env, tdeps, pdeps) |
| } |
| // Interfaces contribute their arg types and tag values, as well as embedded |
| // interfaces. |
| for _, iface := range file.Interfaces { |
| for _, embed := range iface.TransitiveEmbeds() { |
| pdeps[embed.File.Package] = true |
| } |
| for _, method := range iface.Methods { |
| for _, arg := range method.InArgs { |
| addTypeDeps(arg.Type, pkg, env, tdeps, pdeps) |
| } |
| for _, arg := range method.OutArgs { |
| addTypeDeps(arg.Type, pkg, env, tdeps, pdeps) |
| } |
| if stream := method.InStream; stream != nil { |
| addTypeDeps(stream, pkg, env, tdeps, pdeps) |
| } |
| if stream := method.OutStream; stream != nil { |
| addTypeDeps(stream, pkg, env, tdeps, pdeps) |
| } |
| for _, tag := range method.Tags { |
| addValueTypeDeps(tag, pkg, env, tdeps, pdeps) |
| } |
| } |
| } |
| // Errors contribute their param types. |
| for _, def := range file.ErrorDefs { |
| for _, param := range def.Params { |
| addTypeDeps(param.Type, pkg, env, tdeps, pdeps) |
| } |
| } |
| file.TypeDeps = tdeps |
| // Now remove self and built-in package dependencies. Every package can use |
| // itself and the built-in package, so we don't need to record this. |
| delete(pdeps, pkg) |
| delete(pdeps, BuiltInPackage) |
| // Finally populate PackageDeps and sort by package path. |
| file.PackageDeps = make([]*Package, 0, len(pdeps)) |
| for pdep, _ := range pdeps { |
| file.PackageDeps = append(file.PackageDeps, pdep) |
| } |
| sort.Sort(pkgSorter(file.PackageDeps)) |
| } |
| } |
| |
| // Add immediate package deps for t and subtypes of t. |
| func addTypeDeps(t *vdl.Type, pkg *Package, env *Env, tdeps map[*vdl.Type]bool, pdeps map[*Package]bool) { |
| if def := env.typeMap[t]; def != nil { |
| // We don't track transitive dependencies, only immediate dependencies. |
| tdeps[t] = true |
| pdeps[def.File.Package] = true |
| if t == vdl.TypeObjectType { |
| // Special-case: usage of typeobject implies usage of any, since the zero |
| // value for typeobject is any. |
| addTypeDeps(vdl.AnyType, pkg, env, tdeps, pdeps) |
| } |
| return |
| } |
| // Not all types have TypeDefs; e.g. unnamed lists have no corresponding |
| // TypeDef, so we need to traverse those recursively. |
| addSubTypeDeps(t, pkg, env, tdeps, pdeps) |
| } |
| |
| // Add immediate package deps for subtypes of t. |
| func addSubTypeDeps(t *vdl.Type, pkg *Package, env *Env, tdeps map[*vdl.Type]bool, pdeps map[*Package]bool) { |
| switch t.Kind() { |
| case vdl.Array, vdl.List: |
| addTypeDeps(t.Elem(), pkg, env, tdeps, pdeps) |
| case vdl.Set: |
| addTypeDeps(t.Key(), pkg, env, tdeps, pdeps) |
| case vdl.Map: |
| addTypeDeps(t.Key(), pkg, env, tdeps, pdeps) |
| addTypeDeps(t.Elem(), pkg, env, tdeps, pdeps) |
| case vdl.Struct, vdl.Union: |
| for ix := 0; ix < t.NumField(); ix++ { |
| addTypeDeps(t.Field(ix).Type, pkg, env, tdeps, pdeps) |
| } |
| } |
| } |
| |
| // Add immediate package deps for v.Type(), and subvalues. We must traverse the |
| // value to know which types are actually used; e.g. an empty struct doesn't |
| // have a dependency on its field types. |
| // |
| // The purpose of this method is to identify the package and type dependencies |
| // for const or tag values. |
| func addValueTypeDeps(v *vdl.Value, pkg *Package, env *Env, tdeps map[*vdl.Type]bool, pdeps map[*Package]bool) { |
| t := v.Type() |
| if def := env.typeMap[t]; def != nil { |
| tdeps[t] = true |
| pdeps[def.File.Package] = true |
| // Fall through to track transitive dependencies, based on the subvalues. |
| } |
| // Traverse subvalues recursively. |
| switch t.Kind() { |
| case vdl.Array, vdl.List: |
| for ix := 0; ix < v.Len(); ix++ { |
| addValueTypeDeps(v.Index(ix), pkg, env, tdeps, pdeps) |
| } |
| case vdl.Set, vdl.Map: |
| for _, key := range v.Keys() { |
| addValueTypeDeps(key, pkg, env, tdeps, pdeps) |
| if t.Kind() == vdl.Map { |
| addValueTypeDeps(v.MapIndex(key), pkg, env, tdeps, pdeps) |
| } |
| } |
| case vdl.Struct: |
| // There are no subvalues to track if the value is 0. |
| if v.IsZero() { |
| return |
| } |
| for ix := 0; ix < t.NumField(); ix++ { |
| addValueTypeDeps(v.StructField(ix), pkg, env, tdeps, pdeps) |
| } |
| case vdl.Union: |
| _, field := v.UnionField() |
| addValueTypeDeps(field, pkg, env, tdeps, pdeps) |
| case vdl.Any, vdl.Optional: |
| if elem := v.Elem(); elem != nil { |
| addValueTypeDeps(elem, pkg, env, tdeps, pdeps) |
| } |
| case vdl.TypeObject: |
| // TypeObject has dependencies on everything its zero value depends on. |
| addValueTypeDeps(vdl.ZeroValue(v.TypeObject()), pkg, env, tdeps, pdeps) |
| } |
| } |
| |
| // pkgSorter implements sort.Interface, sorting by package path. |
| type pkgSorter []*Package |
| |
| func (s pkgSorter) Len() int { return len(s) } |
| func (s pkgSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
| func (s pkgSorter) Less(i, j int) bool { return s[i].Path < s[j].Path } |