blob: feab45f9bca0a8bca84a1b5e4f328ae970ffbcbc [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// 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 Wang232d6492015-02-25 18:04:54 -08005// Package compile provides utilities to compile vdl files. The Compile
6// function is the main entry point.
7package 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
24import (
25 "path/filepath"
26 "sort"
27
28 "v.io/v23/vdl"
Todd Wang232d6492015-02-25 18:04:54 -080029 "v.io/v23/vdlroot/vdltool"
Jiri Simsaffceefa2015-02-28 11:03:34 -080030 "v.io/x/ref/lib/vdl/parse"
Todd Wang232d6492015-02-25 18:04:54 -080031)
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.
37func 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.
62func 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.
94func 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
103func 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 Wang53a4e2e2015-03-18 10:54:54 -0700110 if _, err := validIdent(pkgName, reservedNormal); err != nil {
111 env.Errors.Errorf("package %s invalid name (%s)", pkgName, err.Error())
Todd Wang232d6492015-02-25 18:04:54 -0800112 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 Wangb90b8de2015-03-31 20:04:13 -0700126 if compileFileDoc(pkg, pfiles, env); !env.Errors.IsEmpty() {
127 return nil
128 }
Todd Wang232d6492015-02-25 18:04:54 -0800129 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 Wangb90b8de2015-03-31 20:04:13 -0700147func 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 Wang232d6492015-02-25 18:04:54 -0800164func 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.
184func 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.
252func 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.
270func 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.
292func 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.
334type pkgSorter []*Package
335
336func (s pkgSorter) Len() int { return len(s) }
337func (s pkgSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
338func (s pkgSorter) Less(i, j int) bool { return s[i].Path < s[j].Path }