blob: de613704e54fdf75d2da82c9dc436352903d51f6 [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 golang
import (
"fmt"
"strconv"
"v.io/v23/vdl"
"v.io/x/ref/lib/vdl/compile"
"v.io/x/ref/lib/vdl/parse"
)
func constDefGo(data goData, def *compile.ConstDef) string {
v := def.Value
return fmt.Sprintf("%s%s %s = %s%s", def.Doc, constOrVar(v.Kind()), def.Name, typedConst(data, v), def.DocSuffix)
}
func constOrVar(k vdl.Kind) string {
switch k {
case vdl.Bool, vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Int16, vdl.Int32, vdl.Int64, vdl.Float32, vdl.Float64, vdl.Complex64, vdl.Complex128, vdl.String, vdl.Enum:
return "const"
}
return "var"
}
func isByteList(t *vdl.Type) bool {
return t.Kind() == vdl.List && t.Elem().Kind() == vdl.Byte
}
func tagValue(data goData, v *vdl.Value) string {
return typedConst(data, vdl.AnyValue(v))
}
// TODO(bprosnitz): Generate the full tag name e.g. security.Read instead of
// security.Label(1)
//
// TODO(toddw): This doesn't work at all if v.Type() is a native type, or has
// subtypes that are native types. It's also broken for optional types that
// can't be represented using a composite literal (e.g. optional primitives).
//
// https://github.com/vanadium/issues/issues/213
func typedConst(data goData, v *vdl.Value) string {
k, t := v.Kind(), v.Type()
if k == vdl.Optional {
if elem := v.Elem(); elem != nil {
return "&" + typedConst(data, elem)
}
return "(" + typeGo(data, t) + ")(nil)" // results in (*Foo)(nil)
}
valstr := untypedConst(data, v)
// Enum, TypeObject and Any already include the type in their values.
// Built-in bool and string are implicitly convertible from literals.
if k == vdl.Enum || k == vdl.TypeObject || k == vdl.Any || t == vdl.BoolType || t == vdl.StringType {
return valstr
}
// Everything else requires an explicit type.
typestr := typeGo(data, t)
// { } are used instead of ( ) for composites
switch k {
case vdl.Array, vdl.Struct:
return typestr + valstr
case vdl.List, vdl.Set, vdl.Map:
// Special-case []byte, which we generate as a type conversion from string,
// and empty variable-length collections, which we generate as a type
// conversion from nil.
if !isByteList(t) && !v.IsZero() {
return typestr + valstr
}
}
return typestr + "(" + valstr + ")"
}
func untypedConst(data goData, v *vdl.Value) string {
k, t := v.Kind(), v.Type()
if isByteList(t) {
if v.IsZero() {
return "nil"
}
return strconv.Quote(string(v.Bytes()))
}
switch k {
case vdl.Any:
if elem := v.Elem(); elem != nil {
// We need to generate a Go expression of type *vdl.Value that represents
// elem. Since the rest of our logic can already generate the Go code for
// any value, we just wrap it in vdl.ValueOf to produce the final result.
//
// This may seem like a strange roundtrip, but results in less generator
// and generated code.
return data.Pkg("v.io/v23/vdl") + "ValueOf(" + typedConst(data, elem) + ")"
}
return "(*" + data.Pkg("v.io/v23/vdl") + "Value)(nil)"
case vdl.Optional:
if elem := v.Elem(); elem != nil {
return untypedConst(data, elem)
}
return "nil"
case vdl.TypeObject:
// We special-case Any and TypeObject, since they cannot be named by the
// user, and are simple to return statically.
switch v.TypeObject().Kind() {
case vdl.Any:
return data.Pkg("v.io/v23/vdl") + "AnyType"
case vdl.TypeObject:
return data.Pkg("v.io/v23/vdl") + "TypeObjectType"
}
// We need to generate a Go expression of type *vdl.Type that represents the
// type. Since the rest of our logic can already generate the Go code for
// any value, we just wrap it in vdl.TypeOf to produce the final result.
//
// This may seem like a strange roundtrip, but results in less generator
// and generated code.
zero := vdl.ZeroValue(v.TypeObject())
return data.Pkg("v.io/v23/vdl") + "TypeOf(" + typedConst(data, zero) + ")"
case vdl.Bool:
return strconv.FormatBool(v.Bool())
case vdl.Byte:
return strconv.FormatUint(uint64(v.Byte()), 10)
case vdl.Uint16, vdl.Uint32, vdl.Uint64:
return strconv.FormatUint(v.Uint(), 10)
case vdl.Int16, vdl.Int32, vdl.Int64:
return strconv.FormatInt(v.Int(), 10)
case vdl.Float32, vdl.Float64:
return formatFloat(v.Float(), k)
case vdl.Complex64, vdl.Complex128:
switch re, im := real(v.Complex()), imag(v.Complex()); {
case im > 0:
return formatFloat(re, k) + "+" + formatFloat(im, k) + "i"
case im < 0:
return formatFloat(re, k) + formatFloat(im, k) + "i"
default:
return formatFloat(re, k)
}
case vdl.String:
return strconv.Quote(v.RawString())
case vdl.Enum:
return typeGo(data, t) + v.EnumLabel()
case vdl.Array:
if v.IsZero() && !t.ContainsKind(vdl.WalkInline, vdl.TypeObject, vdl.Union) {
// We can't rely on the golang zero-value array if t contains inline
// typeobject or union, since the golang zero-value for these types is
// different from the vdl zero-value for these types.
return "{}"
}
s := "{"
for ix := 0; ix < v.Len(); ix++ {
s += "\n" + untypedConst(data, v.Index(ix)) + ","
}
return s + "\n}"
case vdl.List:
if v.IsZero() {
return "nil"
}
s := "{"
for ix := 0; ix < v.Len(); ix++ {
s += "\n" + untypedConst(data, v.Index(ix)) + ","
}
return s + "\n}"
case vdl.Set, vdl.Map:
if v.IsZero() {
return "nil"
}
s := "{"
for _, key := range vdl.SortValuesAsString(v.Keys()) {
s += "\n" + subConst(data, key)
if k == vdl.Set {
s += ": struct{}{},"
} else {
s += ": " + untypedConst(data, v.MapIndex(key)) + ","
}
}
return s + "\n}"
case vdl.Struct:
s := "{"
hasFields := false
for ix := 0; ix < t.NumField(); ix++ {
vf := v.StructField(ix)
if !vf.IsZero() || vf.Type().ContainsKind(vdl.WalkInline, vdl.TypeObject, vdl.Union) {
// We can't rely on the golang zero-value for this field, even if it's a
// vdl zero value, if the field contains inline typeobject or union,
// since the golang zero-value for these types is different from the vdl
// zero-value for these types.
s += "\n" + t.Field(ix).Name + ": " + subConst(data, vf) + ","
hasFields = true
}
}
if hasFields {
s += "\n"
}
return s + "}"
case vdl.Union:
ix, field := v.UnionField()
return typeGo(data, t) + t.Field(ix).Name + "{" + typedConst(data, field) + "}"
default:
data.Env.Errorf(data.File, parse.Pos{}, "%v untypedConst not implemented for %v %v", t, k)
return "INVALID"
}
}
// subConst deals with a quirk regarding Go composite literals. Go allows us to
// elide the type from composite literal Y when the type is implied; basically
// when Y is contained in another composite literal X. However it requires the
// type for Y when X is a struct, and when X is a map and Y is the key. As such
// subConst is called for map keys and struct fields.
func subConst(data goData, v *vdl.Value) string {
switch v.Kind() {
case vdl.Array, vdl.List, vdl.Set, vdl.Map, vdl.Struct, vdl.Optional:
return typedConst(data, v)
}
return untypedConst(data, v)
}
func formatFloat(x float64, kind vdl.Kind) string {
var bitSize int
switch kind {
case vdl.Float32, vdl.Complex64:
bitSize = 32
case vdl.Float64, vdl.Complex128:
bitSize = 64
default:
panic(fmt.Errorf("vdl: formatFloat unhandled kind: %v", kind))
}
return strconv.FormatFloat(x, 'g', -1, bitSize)
}