Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Vanadium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Todd Wang | 232d649 | 2015-02-25 18:04:54 -0800 | [diff] [blame] | 5 | // Package compile provides utilities to compile vdl files. The Compile |
| 6 | // function is the main entry point. |
| 7 | package compile |
| 8 | |
| 9 | // The job of the compiler is to take parse results as input, and output |
| 10 | // compiled results. The concepts between the parser and compiler are very |
| 11 | // similar, thus the naming of parse/compile results is also similar. |
| 12 | // E.g. parse.File represents a parsed file, while compile.File represents a |
| 13 | // compiled file. |
| 14 | // |
| 15 | // The flow of the compiler is contained in the Compile function below, and |
| 16 | // basically defines one concept across all files in the package before moving |
| 17 | // onto the next concept. E.g. we define all types in the package before |
| 18 | // defining all consts in the package. |
| 19 | // |
| 20 | // The logic for simple concepts (e.g. imports) is contained directly in this |
| 21 | // file, while more complicated concepts (types, consts and interfaces) each get |
| 22 | // their own file. |
| 23 | |
| 24 | import ( |
| 25 | "path/filepath" |
| 26 | "sort" |
| 27 | |
| 28 | "v.io/v23/vdl" |
Todd Wang | 232d649 | 2015-02-25 18:04:54 -0800 | [diff] [blame] | 29 | "v.io/v23/vdlroot/vdltool" |
Jiri Simsa | ffceefa | 2015-02-28 11:03:34 -0800 | [diff] [blame] | 30 | "v.io/x/ref/lib/vdl/parse" |
Todd Wang | 232d649 | 2015-02-25 18:04:54 -0800 | [diff] [blame] | 31 | ) |
| 32 | |
| 33 | // CompilePackage compiles a list of parse.Files into a Package. Updates env |
| 34 | // with the compiled package and returns it on success, or returns nil and |
| 35 | // guarantees !env.Errors.IsEmpty(). All imports that the parsed package depend |
| 36 | // on must already have been compiled and populated into env. |
| 37 | func CompilePackage(pkgpath, genpath string, pfiles []*parse.File, config vdltool.Config, env *Env) *Package { |
| 38 | if pkgpath == "" { |
| 39 | env.Errors.Errorf("Compile called with empty pkgpath") |
| 40 | return nil |
| 41 | } |
| 42 | if env.pkgs[pkgpath] != nil { |
| 43 | env.Errors.Errorf("%q invalid recompile (already exists in env)", pkgpath) |
| 44 | return nil |
| 45 | } |
| 46 | pkg := compile(pkgpath, genpath, pfiles, config, env) |
| 47 | if pkg == nil { |
| 48 | return nil |
| 49 | } |
| 50 | if computeDeps(pkg, env); !env.Errors.IsEmpty() { |
| 51 | return nil |
| 52 | } |
| 53 | env.pkgs[pkg.Path] = pkg |
| 54 | return pkg |
| 55 | } |
| 56 | |
| 57 | // CompileConfig compiles a parse.Config into a value. Returns the compiled |
| 58 | // value on success, or returns nil and guarantees !env.Errors.IsEmpty(). All |
| 59 | // imports that the parsed config depend on must already have been compiled and |
| 60 | // populated into env. If t is non-nil, the returned value will be of that |
| 61 | // type. |
| 62 | func CompileConfig(t *vdl.Type, pconfig *parse.Config, env *Env) *vdl.Value { |
| 63 | if pconfig == nil || env == nil { |
| 64 | env.Errors.Errorf("CompileConfig called with nil config or env") |
| 65 | return nil |
| 66 | } |
| 67 | // Since the concepts are so similar between config files and vdl files, we |
| 68 | // just compile it as a single-file vdl package, and compile the exported |
| 69 | // config const to retrieve the final exported config value. |
| 70 | pfile := &parse.File{ |
| 71 | BaseName: filepath.Base(pconfig.FileName), |
| 72 | PackageDef: pconfig.ConfigDef, |
| 73 | Imports: pconfig.Imports, |
| 74 | ConstDefs: pconfig.ConstDefs, |
| 75 | } |
| 76 | pkgpath := filepath.ToSlash(filepath.Dir(pconfig.FileName)) |
| 77 | pkg := compile(pkgpath, pkgpath, []*parse.File{pfile}, vdltool.Config{}, env) |
| 78 | if pkg == nil { |
| 79 | return nil |
| 80 | } |
| 81 | config := compileConst("config", t, pconfig.Config, pkg.Files[0], env) |
| 82 | // Wait to compute deps after we've compiled the config const expression, |
| 83 | // since it might include the only usage of some of the imports. |
| 84 | if computeDeps(pkg, env); !env.Errors.IsEmpty() { |
| 85 | return nil |
| 86 | } |
| 87 | return config |
| 88 | } |
| 89 | |
| 90 | // CompileExpr compiles expr into a value. Returns the compiled value on |
| 91 | // success, or returns nil and guarantees !env.Errors.IsEmpty(). All imports |
| 92 | // that expr depends on must already have been compiled and populated into env. |
| 93 | // If t is non-nil, the returned value will be of that type. |
| 94 | func CompileExpr(t *vdl.Type, expr parse.ConstExpr, env *Env) *vdl.Value { |
| 95 | // Set up a dummy file and compile expr into a value. |
| 96 | file := &File{ |
| 97 | BaseName: "_expr.vdl", |
| 98 | Package: newPackage("_expr", "_expr", "_expr", vdltool.Config{}), |
| 99 | } |
| 100 | return compileConst("expression", t, expr, file, env) |
| 101 | } |
| 102 | |
| 103 | func compile(pkgpath, genpath string, pfiles []*parse.File, config vdltool.Config, env *Env) *Package { |
| 104 | if len(pfiles) == 0 { |
| 105 | env.Errors.Errorf("%q compile called with no files", pkgpath) |
| 106 | return nil |
| 107 | } |
| 108 | // Initialize each file and put it in pkg. |
| 109 | pkgName := parse.InferPackageName(pfiles, env.Errors) |
Todd Wang | 53a4e2e | 2015-03-18 10:54:54 -0700 | [diff] [blame] | 110 | if _, err := validIdent(pkgName, reservedNormal); err != nil { |
| 111 | env.Errors.Errorf("package %s invalid name (%s)", pkgName, err.Error()) |
Todd Wang | 232d649 | 2015-02-25 18:04:54 -0800 | [diff] [blame] | 112 | return nil |
| 113 | } |
| 114 | pkg := newPackage(pkgName, pkgpath, genpath, config) |
| 115 | for _, pfile := range pfiles { |
| 116 | pkg.Files = append(pkg.Files, &File{ |
| 117 | BaseName: pfile.BaseName, |
| 118 | PackageDef: NamePos(pfile.PackageDef), |
| 119 | Package: pkg, |
| 120 | imports: make(map[string]*importPath), |
| 121 | }) |
| 122 | } |
| 123 | // Compile our various structures. The order of these operations matters; |
| 124 | // e.g. we must compile types before consts, since consts may use a type |
| 125 | // defined in this package. |
Todd Wang | b90b8de | 2015-03-31 20:04:13 -0700 | [diff] [blame^] | 126 | if compileFileDoc(pkg, pfiles, env); !env.Errors.IsEmpty() { |
| 127 | return nil |
| 128 | } |
Todd Wang | 232d649 | 2015-02-25 18:04:54 -0800 | [diff] [blame] | 129 | if compileImports(pkg, pfiles, env); !env.Errors.IsEmpty() { |
| 130 | return nil |
| 131 | } |
| 132 | if compileTypeDefs(pkg, pfiles, env); !env.Errors.IsEmpty() { |
| 133 | return nil |
| 134 | } |
| 135 | if compileErrorDefs(pkg, pfiles, env); !env.Errors.IsEmpty() { |
| 136 | return nil |
| 137 | } |
| 138 | if compileConstDefs(pkg, pfiles, env); !env.Errors.IsEmpty() { |
| 139 | return nil |
| 140 | } |
| 141 | if compileInterfaces(pkg, pfiles, env); !env.Errors.IsEmpty() { |
| 142 | return nil |
| 143 | } |
| 144 | return pkg |
| 145 | } |
| 146 | |
Todd Wang | b90b8de | 2015-03-31 20:04:13 -0700 | [diff] [blame^] | 147 | func compileFileDoc(pkg *Package, pfiles []*parse.File, env *Env) { |
| 148 | for index := range pfiles { |
| 149 | file, pfile := pkg.Files[index], pfiles[index] |
| 150 | if index == 0 { |
| 151 | pkg.FileDoc = pfile.Doc |
| 152 | } else if pkg.FileDoc != pfile.Doc { |
| 153 | // We force all file-doc to be the same, since *.vdl files aren't 1-to-1 |
| 154 | // with the generated files in each language, e.g. Java creates one file |
| 155 | // per class, while Javascript creates a single file for the entire |
| 156 | // package. For the common-case where we use file-doc for copyright |
| 157 | // headers, it also prevents the user from accidentally adding copyright |
| 158 | // headers to one file but not another, in the same package. |
| 159 | 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)") |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | |
Todd Wang | 232d649 | 2015-02-25 18:04:54 -0800 | [diff] [blame] | 164 | func compileImports(pkg *Package, pfiles []*parse.File, env *Env) { |
| 165 | for index := range pfiles { |
| 166 | file, pfile := pkg.Files[index], pfiles[index] |
| 167 | for _, pimp := range pfile.Imports { |
| 168 | if dep := env.ResolvePackage(pimp.Path); dep == nil { |
| 169 | env.Errorf(file, pimp.Pos, "import path %q not found", pimp.Path) |
| 170 | } |
| 171 | local := pimp.LocalName() |
| 172 | if dup := file.imports[local]; dup != nil { |
| 173 | env.Errorf(file, pimp.Pos, "import %s reused (previous at %s)", local, dup.pos) |
| 174 | continue |
| 175 | } |
| 176 | file.imports[local] = &importPath{pimp.Path, pimp.Pos, false} |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | // TODO(toddw): Remove this function and all helpers, after all code generators |
| 182 | // have been updated to compute their own dependencies. The only code that will |
| 183 | // remain below this point is the loop checking for unused imports. |
| 184 | func computeDeps(pkg *Package, env *Env) { |
| 185 | // Check for unused user-supplied imports. |
| 186 | for _, file := range pkg.Files { |
| 187 | for _, imp := range file.imports { |
| 188 | if !imp.used { |
| 189 | env.Errorf(file, imp.pos, "import path %q unused", imp.path) |
| 190 | } |
| 191 | } |
| 192 | } |
| 193 | // Compute type and package dependencies per-file, based on the types and |
| 194 | // interfaces that are actually used. We ignore const dependencies, since |
| 195 | // we've already evaluated the const expressions. |
| 196 | for _, file := range pkg.Files { |
| 197 | tdeps := make(map[*vdl.Type]bool) |
| 198 | pdeps := make(map[*Package]bool) |
| 199 | // TypeDef.Type is always defined in our package; start with sub types. |
| 200 | for _, def := range file.TypeDefs { |
| 201 | addSubTypeDeps(def.Type, pkg, env, tdeps, pdeps) |
| 202 | } |
| 203 | // Consts contribute their value types. |
| 204 | for _, def := range file.ConstDefs { |
| 205 | addValueTypeDeps(def.Value, pkg, env, tdeps, pdeps) |
| 206 | } |
| 207 | // Interfaces contribute their arg types and tag values, as well as embedded |
| 208 | // interfaces. |
| 209 | for _, iface := range file.Interfaces { |
| 210 | for _, embed := range iface.TransitiveEmbeds() { |
| 211 | pdeps[embed.File.Package] = true |
| 212 | } |
| 213 | for _, method := range iface.Methods { |
| 214 | for _, arg := range method.InArgs { |
| 215 | addTypeDeps(arg.Type, pkg, env, tdeps, pdeps) |
| 216 | } |
| 217 | for _, arg := range method.OutArgs { |
| 218 | addTypeDeps(arg.Type, pkg, env, tdeps, pdeps) |
| 219 | } |
| 220 | if stream := method.InStream; stream != nil { |
| 221 | addTypeDeps(stream, pkg, env, tdeps, pdeps) |
| 222 | } |
| 223 | if stream := method.OutStream; stream != nil { |
| 224 | addTypeDeps(stream, pkg, env, tdeps, pdeps) |
| 225 | } |
| 226 | for _, tag := range method.Tags { |
| 227 | addValueTypeDeps(tag, pkg, env, tdeps, pdeps) |
| 228 | } |
| 229 | } |
| 230 | } |
| 231 | // Errors contribute their param types. |
| 232 | for _, def := range file.ErrorDefs { |
| 233 | for _, param := range def.Params { |
| 234 | addTypeDeps(param.Type, pkg, env, tdeps, pdeps) |
| 235 | } |
| 236 | } |
| 237 | file.TypeDeps = tdeps |
| 238 | // Now remove self and built-in package dependencies. Every package can use |
| 239 | // itself and the built-in package, so we don't need to record this. |
| 240 | delete(pdeps, pkg) |
| 241 | delete(pdeps, BuiltInPackage) |
| 242 | // Finally populate PackageDeps and sort by package path. |
| 243 | file.PackageDeps = make([]*Package, 0, len(pdeps)) |
| 244 | for pdep, _ := range pdeps { |
| 245 | file.PackageDeps = append(file.PackageDeps, pdep) |
| 246 | } |
| 247 | sort.Sort(pkgSorter(file.PackageDeps)) |
| 248 | } |
| 249 | } |
| 250 | |
| 251 | // Add immediate package deps for t and subtypes of t. |
| 252 | func addTypeDeps(t *vdl.Type, pkg *Package, env *Env, tdeps map[*vdl.Type]bool, pdeps map[*Package]bool) { |
| 253 | if def := env.typeDefs[t]; def != nil { |
| 254 | // We don't track transitive dependencies, only immediate dependencies. |
| 255 | tdeps[t] = true |
| 256 | pdeps[def.File.Package] = true |
| 257 | if t == vdl.TypeObjectType { |
| 258 | // Special-case: usage of typeobject implies usage of any, since the zero |
| 259 | // value for typeobject is any. |
| 260 | addTypeDeps(vdl.AnyType, pkg, env, tdeps, pdeps) |
| 261 | } |
| 262 | return |
| 263 | } |
| 264 | // Not all types have TypeDefs; e.g. unnamed lists have no corresponding |
| 265 | // TypeDef, so we need to traverse those recursively. |
| 266 | addSubTypeDeps(t, pkg, env, tdeps, pdeps) |
| 267 | } |
| 268 | |
| 269 | // Add immediate package deps for subtypes of t. |
| 270 | func addSubTypeDeps(t *vdl.Type, pkg *Package, env *Env, tdeps map[*vdl.Type]bool, pdeps map[*Package]bool) { |
| 271 | switch t.Kind() { |
| 272 | case vdl.Array, vdl.List: |
| 273 | addTypeDeps(t.Elem(), pkg, env, tdeps, pdeps) |
| 274 | case vdl.Set: |
| 275 | addTypeDeps(t.Key(), pkg, env, tdeps, pdeps) |
| 276 | case vdl.Map: |
| 277 | addTypeDeps(t.Key(), pkg, env, tdeps, pdeps) |
| 278 | addTypeDeps(t.Elem(), pkg, env, tdeps, pdeps) |
| 279 | case vdl.Struct, vdl.Union: |
| 280 | for ix := 0; ix < t.NumField(); ix++ { |
| 281 | addTypeDeps(t.Field(ix).Type, pkg, env, tdeps, pdeps) |
| 282 | } |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | // Add immediate package deps for v.Type(), and subvalues. We must traverse the |
| 287 | // value to know which types are actually used; e.g. an empty struct doesn't |
| 288 | // have a dependency on its field types. |
| 289 | // |
| 290 | // The purpose of this method is to identify the package and type dependencies |
| 291 | // for const or tag values. |
| 292 | func addValueTypeDeps(v *vdl.Value, pkg *Package, env *Env, tdeps map[*vdl.Type]bool, pdeps map[*Package]bool) { |
| 293 | t := v.Type() |
| 294 | if def := env.typeDefs[t]; def != nil { |
| 295 | tdeps[t] = true |
| 296 | pdeps[def.File.Package] = true |
| 297 | // Fall through to track transitive dependencies, based on the subvalues. |
| 298 | } |
| 299 | // Traverse subvalues recursively. |
| 300 | switch t.Kind() { |
| 301 | case vdl.Array, vdl.List: |
| 302 | for ix := 0; ix < v.Len(); ix++ { |
| 303 | addValueTypeDeps(v.Index(ix), pkg, env, tdeps, pdeps) |
| 304 | } |
| 305 | case vdl.Set, vdl.Map: |
| 306 | for _, key := range v.Keys() { |
| 307 | addValueTypeDeps(key, pkg, env, tdeps, pdeps) |
| 308 | if t.Kind() == vdl.Map { |
| 309 | addValueTypeDeps(v.MapIndex(key), pkg, env, tdeps, pdeps) |
| 310 | } |
| 311 | } |
| 312 | case vdl.Struct: |
| 313 | // There are no subvalues to track if the value is 0. |
| 314 | if v.IsZero() { |
| 315 | return |
| 316 | } |
| 317 | for ix := 0; ix < t.NumField(); ix++ { |
| 318 | addValueTypeDeps(v.StructField(ix), pkg, env, tdeps, pdeps) |
| 319 | } |
| 320 | case vdl.Union: |
| 321 | _, field := v.UnionField() |
| 322 | addValueTypeDeps(field, pkg, env, tdeps, pdeps) |
| 323 | case vdl.Any, vdl.Optional: |
| 324 | if elem := v.Elem(); elem != nil { |
| 325 | addValueTypeDeps(elem, pkg, env, tdeps, pdeps) |
| 326 | } |
| 327 | case vdl.TypeObject: |
| 328 | // TypeObject has dependencies on everything its zero value depends on. |
| 329 | addValueTypeDeps(vdl.ZeroValue(v.TypeObject()), pkg, env, tdeps, pdeps) |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | // pkgSorter implements sort.Interface, sorting by package path. |
| 334 | type pkgSorter []*Package |
| 335 | |
| 336 | func (s pkgSorter) Len() int { return len(s) } |
| 337 | func (s pkgSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
| 338 | func (s pkgSorter) Less(i, j int) bool { return s[i].Path < s[j].Path } |