| // 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 |
| |
| import ( |
| "fmt" |
| "math/big" |
| |
| "v.io/v23/vdl" |
| "v.io/x/lib/toposort" |
| "v.io/x/ref/lib/vdl/opconst" |
| "v.io/x/ref/lib/vdl/parse" |
| ) |
| |
| var ( |
| // Built-in consts defined by the compiler. |
| NilConst = vdl.ZeroValue(vdl.AnyType) // nil == any(nil) |
| TrueConst = vdl.BoolValue(nil, true) |
| FalseConst = vdl.BoolValue(nil, false) |
| ) |
| |
| // ConstDef represents a user-defined named const definition in the compiled |
| // results. |
| type ConstDef struct { |
| NamePos // name, parse position and docs |
| Exported bool // is this const definition exported? |
| Value *vdl.Value // const value |
| File *File // parent file that this const is defined in |
| } |
| |
| func (x *ConstDef) String() string { |
| y := *x |
| y.File = nil // avoid infinite loop |
| return fmt.Sprintf("%+v", y) |
| } |
| |
| // compileConstDefs is the "entry point" to the rest of this file. It takes the |
| // consts defined in pfiles and compiles them into ConstDefs in pkg. |
| func compileConstDefs(pkg *Package, pfiles []*parse.File, env *Env) { |
| cd := constDefiner{pkg, pfiles, env, make(map[string]*constDefBuilder)} |
| if cd.Declare(); !env.Errors.IsEmpty() { |
| return |
| } |
| cd.Define() |
| } |
| |
| // constDefiner defines consts in a package. This is split into two phases: |
| // 1) Declare ensures local const references can be resolved. |
| // 2) Define sorts in dependency order, and evaluates and defines each const. |
| // |
| // It holds a builders map from const name to constDefBuilder, where the |
| // constDefBuilder is responsible for compiling and defining a single const. |
| type constDefiner struct { |
| pkg *Package |
| pfiles []*parse.File |
| env *Env |
| builders map[string]*constDefBuilder |
| } |
| |
| type constDefBuilder struct { |
| def *ConstDef |
| pexpr parse.ConstExpr |
| } |
| |
| func printConstBuilderName(ibuilder interface{}) string { |
| return ibuilder.(*constDefBuilder).def.Name |
| } |
| |
| // Declare creates builders for each const defined in the package. |
| func (cd constDefiner) Declare() { |
| for ix := range cd.pkg.Files { |
| file, pfile := cd.pkg.Files[ix], cd.pfiles[ix] |
| for _, pdef := range pfile.ConstDefs { |
| export, err := validConstIdent(pdef.Name, reservedNormal) |
| if err != nil { |
| cd.env.prefixErrorf(file, pdef.Pos, err, "const %s invalid name", pdef.Name) |
| continue // keep going to catch more errors |
| } |
| detail := identDetail("const", file, pdef.Pos) |
| if err := file.DeclareIdent(pdef.Name, detail); err != nil { |
| cd.env.prefixErrorf(file, pdef.Pos, err, "const %s name conflict", pdef.Name) |
| continue |
| } |
| def := &ConstDef{NamePos: NamePos(pdef.NamePos), Exported: export, File: file} |
| cd.builders[pdef.Name] = &constDefBuilder{def, pdef.Expr} |
| } |
| } |
| } |
| |
| // Define consts. We sort by dependencies on other named consts in this |
| // package. We don't allow cycles. The ordering is necessary to perform simple |
| // single-pass evaluation. |
| // |
| // The dependency analysis is performed on consts, not the files they occur in; |
| // consts in the same package may be defined in any file, even if they cause |
| // cyclic file dependencies. |
| func (cd constDefiner) Define() { |
| // Populate sorter with dependency information. The sorting ensures that the |
| // list of const defs within each file is topologically sorted, and also |
| // deterministic; other than dependencies, const defs are listed in the same |
| // order they were defined in the parsed files. |
| var sorter toposort.Sorter |
| for _, pfile := range cd.pfiles { |
| for _, pdef := range pfile.ConstDefs { |
| b := cd.builders[pdef.Name] |
| sorter.AddNode(b) |
| for _, dep := range cd.getLocalDeps(b.pexpr) { |
| sorter.AddEdge(b, dep) |
| } |
| } |
| } |
| // Sort and check for cycles. |
| sorted, cycles := sorter.Sort() |
| if len(cycles) > 0 { |
| cycleStr := toposort.DumpCycles(cycles, printConstBuilderName) |
| first := cycles[0][0].(*constDefBuilder) |
| cd.env.Errorf(first.def.File, first.def.Pos, "package %v has cyclic consts: %v", cd.pkg.Name, cycleStr) |
| return |
| } |
| // Define all consts. Since we add the const defs as we go and evaluate in |
| // topological order, dependencies are guaranteed to be resolvable when we get |
| // around to evaluating the consts that depend on them. |
| for _, ibuilder := range sorted { |
| b := ibuilder.(*constDefBuilder) |
| def, file := b.def, b.def.File |
| def.Value = compileConst("const", nil, b.pexpr, file, cd.env) |
| if def.Value == nil { |
| continue |
| } |
| // If the const is exported, make sure that the value type is exported. We |
| // only care about the final value type; we can't perform this check while |
| // we're compiling and evaluating the const, since it's fine for sub |
| // expressions to use unexported types. |
| if def.Exported && !typeIsExported(def.Value.Type(), cd.env) { |
| cd.env.Errorf(file, def.Pos, "const %s must have a transitively exported type", def.Name) |
| } |
| addConstDef(def, cd.env) |
| } |
| } |
| |
| // addConstDef updates our various structures to add a new const def. |
| func addConstDef(def *ConstDef, env *Env) { |
| def.File.ConstDefs = append(def.File.ConstDefs, def) |
| def.File.Package.constDefs = append(def.File.Package.constDefs, def) |
| def.File.Package.constMap[def.Name] = def |
| if env != nil { |
| // env should only be nil during initialization of the built-in package; |
| // NewEnv ensures new environments have the built-in consts. |
| env.constMap[def.Value] = def |
| } |
| } |
| |
| // getLocalDeps returns a list of named const dependencies for pexpr, where each |
| // dependency is defined in this package. |
| func (cd constDefiner) getLocalDeps(pexpr parse.ConstExpr) (deps []*constDefBuilder) { |
| switch pe := pexpr.(type) { |
| case nil, *parse.ConstLit, *parse.ConstTypeObject: |
| // These have no deps. |
| case *parse.ConstNamed: |
| // Named references to other consts in this package are all we care about. |
| if b := cd.builders[pe.Name]; b != nil { |
| deps = append(deps, b) |
| } |
| case *parse.ConstCompositeLit: |
| for _, kv := range pe.KVList { |
| deps = append(deps, cd.getLocalDeps(kv.Key)...) |
| deps = append(deps, cd.getLocalDeps(kv.Value)...) |
| } |
| case *parse.ConstIndexed: |
| deps = append(deps, cd.getLocalDeps(pe.Expr)...) |
| deps = append(deps, cd.getLocalDeps(pe.IndexExpr)...) |
| case *parse.ConstTypeConv: |
| deps = append(deps, cd.getLocalDeps(pe.Expr)...) |
| case *parse.ConstUnaryOp: |
| deps = append(deps, cd.getLocalDeps(pe.Expr)...) |
| case *parse.ConstBinaryOp: |
| deps = append(deps, cd.getLocalDeps(pe.Lexpr)...) |
| deps = append(deps, cd.getLocalDeps(pe.Rexpr)...) |
| default: |
| panic(fmt.Errorf("vdl: unhandled parse.ConstExpr %T %#v", pexpr, pexpr)) |
| } |
| return |
| } |
| |
| // compileConst compiles pexpr into a *vdl.Value. All named types and consts |
| // referenced by pexpr must already be defined. |
| // |
| // The implicit type is applied to pexpr; untyped consts and composite literals |
| // with no explicit type assume the implicit type. Errors are reported if the |
| // implicit type isn't assignable from the final value. If the implicit type is |
| // nil, the exported config const must be explicitly typed. |
| func compileConst(what string, implicit *vdl.Type, pexpr parse.ConstExpr, file *File, env *Env) *vdl.Value { |
| c := evalConstExpr(implicit, pexpr, file, env) |
| if !c.IsValid() { |
| return nil |
| } |
| if implicit != nil && |
| (c.Type() == nil || |
| (implicit.CanBeOptional() && vdl.OptionalType(implicit) == c.Type()) || |
| (c.Type().CanBeOptional() && vdl.OptionalType(c.Type()) == implicit)) { |
| // Convert untyped const into the implicit type. |
| conv, err := c.Convert(implicit) |
| if err != nil { |
| env.prefixErrorf(file, pexpr.Pos(), err, "invalid %s", what) |
| return nil |
| } |
| c = conv |
| } |
| v, err := c.ToValue() |
| if err != nil { |
| env.prefixErrorf(file, pexpr.Pos(), err, "invalid %s", what) |
| return nil |
| } |
| if implicit != nil && !implicit.AssignableFrom(v) { |
| env.Errorf(file, pexpr.Pos(), "invalid %v (%v not assignable from %v)", what, implicit, v) |
| return nil |
| } |
| return v |
| } |
| |
| // compileConstExplicit is similar to compileConst, but instead of an optional |
| // implicit type, requires a non-nil explicit type. The compiled const is |
| // explicitly converted to the explicit type. |
| func compileConstExplicit(what string, explicit *vdl.Type, pexpr parse.ConstExpr, file *File, env *Env) *vdl.Value { |
| c := evalConstExpr(explicit, pexpr, file, env) |
| if !c.IsValid() { |
| return nil |
| } |
| conv, err := c.Convert(explicit) |
| if err != nil { |
| env.prefixErrorf(file, pexpr.Pos(), err, "invalid %v", what) |
| return nil |
| } |
| v, err := conv.ToValue() |
| if err != nil { |
| env.prefixErrorf(file, pexpr.Pos(), err, "invalid %s", what) |
| return nil |
| } |
| return v |
| } |
| |
| var bigRatZero = new(big.Rat) |
| |
| // evalConstExpr returns the result of evaluating pexpr into a opconst.Const. |
| // If implicit is non-nil, we apply it to pexpr if it doesn't have an explicit |
| // type specified. E.g. composite literals and enum labels with no explicit |
| // type assume the implicit type. |
| func evalConstExpr(implicit *vdl.Type, pexpr parse.ConstExpr, file *File, env *Env) opconst.Const { |
| switch pe := pexpr.(type) { |
| case *parse.ConstLit: |
| // All literal constants start out untyped. |
| switch tlit := pe.Lit.(type) { |
| case string: |
| return opconst.String(tlit) |
| case *big.Int: |
| return opconst.Integer(tlit) |
| case *big.Rat: |
| return opconst.Rational(tlit) |
| default: |
| panic(fmt.Errorf("vdl: unhandled parse.ConstLit %T %#v", tlit, tlit)) |
| } |
| case *parse.ConstCompositeLit: |
| t := implicit |
| if pe.Type != nil { |
| // If an explicit type is specified for the composite literal, it |
| // overrides the implicit type. |
| t = compileType(pe.Type, file, env) |
| if t == nil { |
| break |
| } |
| } |
| v := evalCompLit(t, pe, file, env) |
| if v == nil { |
| break |
| } |
| return opconst.FromValue(v) |
| case *parse.ConstNamed: |
| c, err := env.EvalConst(pe.Name, file) |
| if err != nil { |
| if implicit != nil { |
| // Try applying the name as a selector against the implicit type. This |
| // allows a shortened form for enum labels, without redundantly |
| // specifying the enum type. |
| if c, err2 := env.evalSelectorOnType(implicit, pe.Name); err2 == nil { |
| return c |
| } |
| } |
| env.prefixErrorf(file, pe.Pos(), err, "const %s invalid", pe.Name) |
| break |
| } |
| return c |
| case *parse.ConstIndexed: |
| value := compileConst("const", nil, pe.Expr, file, env) |
| if value == nil { |
| break |
| } |
| // TODO(bprosnitz) Should indexing on set also be supported? |
| switch value.Kind() { |
| case vdl.Array, vdl.List: |
| v := evalListIndex(value, pe.IndexExpr, file, env) |
| if v != nil { |
| return opconst.FromValue(v) |
| } |
| case vdl.Map: |
| v := evalMapIndex(value, pe.IndexExpr, file, env) |
| if v != nil { |
| return opconst.FromValue(v) |
| } |
| default: |
| env.Errorf(file, pe.Pos(), "illegal use of index operator with unsupported type") |
| } |
| case *parse.ConstTypeConv: |
| t := compileType(pe.Type, file, env) |
| x := evalConstExpr(nil, pe.Expr, file, env) |
| if t == nil || !x.IsValid() { |
| break |
| } |
| res, err := x.Convert(t) |
| if err != nil { |
| env.prefixErrorf(file, pe.Pos(), err, "invalid type conversion") |
| break |
| } |
| return res |
| case *parse.ConstTypeObject: |
| t := compileType(pe.Type, file, env) |
| if t == nil { |
| break |
| } |
| return opconst.FromValue(vdl.TypeObjectValue(t)) |
| case *parse.ConstUnaryOp: |
| x := evalConstExpr(nil, pe.Expr, file, env) |
| op := opconst.ToUnaryOp(pe.Op) |
| if op == opconst.InvalidUnaryOp { |
| env.Errorf(file, pe.Pos(), "unary %s undefined", pe.Op) |
| break |
| } |
| if !x.IsValid() { |
| break |
| } |
| res, err := opconst.EvalUnary(op, x) |
| if err != nil { |
| env.prefixErrorf(file, pe.Pos(), err, "unary %s invalid", pe.Op) |
| break |
| } |
| return res |
| case *parse.ConstBinaryOp: |
| x := evalConstExpr(nil, pe.Lexpr, file, env) |
| y := evalConstExpr(nil, pe.Rexpr, file, env) |
| op := opconst.ToBinaryOp(pe.Op) |
| if op == opconst.InvalidBinaryOp { |
| env.Errorf(file, pe.Pos(), "binary %s undefined", pe.Op) |
| break |
| } |
| if !x.IsValid() || !y.IsValid() { |
| break |
| } |
| res, err := opconst.EvalBinary(op, x, y) |
| if err != nil { |
| env.prefixErrorf(file, pe.Pos(), err, "binary %s invalid", pe.Op) |
| break |
| } |
| return res |
| default: |
| panic(fmt.Errorf("vdl: unhandled parse.ConstExpr %T %#v", pexpr, pexpr)) |
| } |
| return opconst.Const{} |
| } |
| |
| // evalListIndex evalutes base[index], where base is a list or array. |
| func evalListIndex(base *vdl.Value, indexExpr parse.ConstExpr, file *File, env *Env) *vdl.Value { |
| index := compileConstExplicit(base.Kind().String()+" index", vdl.Uint64Type, indexExpr, file, env) |
| if index == nil { |
| return nil |
| } |
| ix := int(index.Uint()) |
| if ix >= base.Len() { |
| env.Errorf(file, indexExpr.Pos(), "index %d out of range", ix) |
| return nil |
| } |
| return base.Index(ix) |
| } |
| |
| // evalMapIndex evaluates base[index], where base is a map. |
| func evalMapIndex(base *vdl.Value, indexExpr parse.ConstExpr, file *File, env *Env) *vdl.Value { |
| key := compileConst("map key", base.Type().Key(), indexExpr, file, env) |
| if key == nil { |
| return nil |
| } |
| item := base.MapIndex(key) |
| if item == nil { |
| // Unlike normal go code, it is probably undesirable to return the zero |
| // value here. It is very likely this is an error. |
| env.Errorf(file, indexExpr.Pos(), "map key %v not found in map", key) |
| return nil |
| } |
| return item |
| } |
| |
| // evalCompLit evaluates a composite literal, returning it as a vdl.Value. The |
| // type t is required, but note that subtypes enclosed in a composite type can |
| // always use the implicit type from the parent composite type. |
| func evalCompLit(t *vdl.Type, lit *parse.ConstCompositeLit, file *File, env *Env) *vdl.Value { |
| if t == nil { |
| env.Errorf(file, lit.Pos(), "missing type for composite literal") |
| return nil |
| } |
| isOptional := false |
| if t.Kind() == vdl.Optional { |
| isOptional = true |
| t = t.Elem() |
| } |
| var v *vdl.Value |
| switch t.Kind() { |
| case vdl.Array, vdl.List: |
| v = evalListLit(t, lit, file, env) |
| case vdl.Set: |
| v = evalSetLit(t, lit, file, env) |
| case vdl.Map: |
| v = evalMapLit(t, lit, file, env) |
| case vdl.Struct: |
| v = evalStructLit(t, lit, file, env) |
| case vdl.Union: |
| v = evalUnionLit(t, lit, file, env) |
| default: |
| env.Errorf(file, lit.Pos(), "%v invalid type for composite literal", t) |
| return nil |
| } |
| if v != nil && isOptional { |
| v = vdl.OptionalValue(v) |
| } |
| return v |
| } |
| |
| func evalListLit(t *vdl.Type, lit *parse.ConstCompositeLit, file *File, env *Env) *vdl.Value { |
| listv := vdl.ZeroValue(t) |
| desc := fmt.Sprintf("%v %s literal", t, t.Kind()) |
| var index int |
| assigned := make(map[int]bool) |
| for _, kv := range lit.KVList { |
| if kv.Value == nil { |
| env.Errorf(file, lit.Pos(), "missing value in %s", desc) |
| return nil |
| } |
| // Set the index to the key, if it exists. Semantics are looser than |
| // values; we allow any key that's convertible to uint64, even if the key is |
| // already typed. |
| if kv.Key != nil { |
| key := compileConstExplicit("list index", vdl.Uint64Type, kv.Key, file, env) |
| if key == nil { |
| return nil |
| } |
| index = int(key.Uint()) |
| } |
| // Make sure the index hasn't been assigned already, and adjust the list |
| // length as necessary. |
| if assigned[index] { |
| env.Errorf(file, kv.Value.Pos(), "duplicate index %d in %s", index, desc) |
| return nil |
| } |
| assigned[index] = true |
| if index >= listv.Len() { |
| if t.Kind() == vdl.Array { |
| env.Errorf(file, kv.Value.Pos(), "index %d out of range in %s", index, desc) |
| return nil |
| } |
| listv.AssignLen(index + 1) |
| } |
| // Evaluate the value and perform the assignment. |
| value := compileConst(t.Kind().String()+" value", t.Elem(), kv.Value, file, env) |
| if value == nil { |
| return nil |
| } |
| listv.Index(index).Assign(value) |
| index++ |
| } |
| return listv |
| } |
| |
| func evalSetLit(t *vdl.Type, lit *parse.ConstCompositeLit, file *File, env *Env) *vdl.Value { |
| setv := vdl.ZeroValue(t) |
| desc := fmt.Sprintf("%v set literal", t) |
| for _, kv := range lit.KVList { |
| if kv.Key != nil { |
| env.Errorf(file, kv.Key.Pos(), "invalid index in %s", desc) |
| return nil |
| } |
| if kv.Value == nil { |
| env.Errorf(file, lit.Pos(), "missing key in %s", desc) |
| return nil |
| } |
| // Evaluate the key and make sure it hasn't been assigned already. |
| key := compileConst("set key", t.Key(), kv.Value, file, env) |
| if key == nil { |
| return nil |
| } |
| if setv.ContainsKey(key) { |
| env.Errorf(file, kv.Value.Pos(), "duplicate key %v in %s", key, desc) |
| return nil |
| } |
| setv.AssignSetKey(key) |
| } |
| return setv |
| } |
| |
| func evalMapLit(t *vdl.Type, lit *parse.ConstCompositeLit, file *File, env *Env) *vdl.Value { |
| mapv := vdl.ZeroValue(t) |
| desc := fmt.Sprintf("%v map literal", t) |
| for _, kv := range lit.KVList { |
| if kv.Key == nil { |
| env.Errorf(file, lit.Pos(), "missing key in %s", desc) |
| return nil |
| } |
| if kv.Value == nil { |
| env.Errorf(file, lit.Pos(), "missing elem in %s", desc) |
| return nil |
| } |
| // Evaluate the key and make sure it hasn't been assigned already. |
| key := compileConst("map key", t.Key(), kv.Key, file, env) |
| if key == nil { |
| return nil |
| } |
| if mapv.ContainsKey(key) { |
| env.Errorf(file, kv.Key.Pos(), "duplicate key %v in %s", key, desc) |
| return nil |
| } |
| // Evaluate the value and perform the assignment. |
| value := compileConst("map value", t.Elem(), kv.Value, file, env) |
| if value == nil { |
| return nil |
| } |
| mapv.AssignMapIndex(key, value) |
| } |
| return mapv |
| } |
| |
| func evalStructLit(t *vdl.Type, lit *parse.ConstCompositeLit, file *File, env *Env) *vdl.Value { |
| // We require that either all items have keys, or none of them do. |
| structv := vdl.ZeroValue(t) |
| desc := fmt.Sprintf("%v struct literal", t) |
| haskeys := len(lit.KVList) > 0 && lit.KVList[0].Key != nil |
| assigned := make(map[int]bool) |
| for index, kv := range lit.KVList { |
| if kv.Value == nil { |
| env.Errorf(file, lit.Pos(), "missing field value in %s", desc) |
| return nil |
| } |
| if haskeys != (kv.Key != nil) { |
| env.Errorf(file, kv.Value.Pos(), "mixed key:value and value in %s", desc) |
| return nil |
| } |
| // Get the field description, either from the key or the index. |
| var field vdl.Field |
| if kv.Key != nil { |
| // There is an explicit field name specified. |
| fname, ok := kv.Key.(*parse.ConstNamed) |
| if !ok { |
| env.Errorf(file, kv.Key.Pos(), "invalid field name %q in %s", kv.Key.String(), desc) |
| return nil |
| } |
| field, index = t.FieldByName(fname.Name) |
| if index < 0 { |
| env.Errorf(file, kv.Key.Pos(), "unknown field %q in %s", fname.Name, desc) |
| return nil |
| } |
| } else { |
| // No field names, just use the index position. |
| if index >= t.NumField() { |
| env.Errorf(file, kv.Value.Pos(), "too many fields in %s", desc) |
| return nil |
| } |
| field = t.Field(index) |
| } |
| // Make sure the field hasn't been assigned already. |
| if assigned[index] { |
| env.Errorf(file, kv.Value.Pos(), "duplicate field %q in %s", field.Name, desc) |
| return nil |
| } |
| assigned[index] = true |
| // Evaluate the value and perform the assignment. |
| value := compileConst("struct field", field.Type, kv.Value, file, env) |
| if value == nil { |
| return nil |
| } |
| structv.StructField(index).Assign(value) |
| } |
| if !haskeys && 0 < len(assigned) && len(assigned) < t.NumField() { |
| env.Errorf(file, lit.Pos(), "too few fields in %s", desc) |
| return nil |
| } |
| return structv |
| } |
| |
| func evalUnionLit(t *vdl.Type, lit *parse.ConstCompositeLit, file *File, env *Env) *vdl.Value { |
| // We require either an empty kv list, or exactly one kv with an explicit key. |
| unionv := vdl.ZeroValue(t) |
| if len(lit.KVList) == 0 { |
| return unionv |
| } |
| desc := fmt.Sprintf("%v union literal", t) |
| if len(lit.KVList) != 1 { |
| env.Errorf(file, lit.Pos(), "invalid %s (must have a single entry)", desc) |
| return nil |
| } |
| kv := lit.KVList[0] |
| if kv.Key == nil || kv.Value == nil { |
| env.Errorf(file, lit.Pos(), "invalid %s (must have explicit key and value)", desc) |
| return nil |
| } |
| // Get the field description. |
| fname, ok := kv.Key.(*parse.ConstNamed) |
| if !ok { |
| env.Errorf(file, kv.Key.Pos(), "invalid field name %q in %s", kv.Key.String(), desc) |
| return nil |
| } |
| field, index := t.FieldByName(fname.Name) |
| if index < 0 { |
| env.Errorf(file, kv.Key.Pos(), "unknown field %q in %s", fname.Name, desc) |
| return nil |
| } |
| // Evaluate the value and perform the assignment. |
| value := compileConst("union field", field.Type, kv.Value, file, env) |
| if value == nil { |
| return nil |
| } |
| unionv.AssignField(index, value) |
| return unionv |
| } |