| // 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 vdlgen |
| |
| // TODO(toddw): Add tests. |
| |
| import ( |
| "fmt" |
| "io" |
| "sort" |
| "strings" |
| |
| "v.io/v23/vdl" |
| "v.io/v23/vdlroot/signature" |
| "v.io/x/lib/textutil" |
| ) |
| |
| // NamedTypes represents a set of unique named types. The main usage is to |
| // collect the named types from one or more signatures, and to print all of the |
| // named types. |
| type NamedTypes struct { |
| types map[*vdl.Type]bool |
| } |
| |
| // Add adds to x all named types from the type graph rooted at t. |
| func (x *NamedTypes) Add(t *vdl.Type) { |
| if t == nil { |
| return |
| } |
| t.Walk(vdl.WalkAll, func(t *vdl.Type) bool { |
| if t.Name() != "" { |
| if x.types == nil { |
| x.types = make(map[*vdl.Type]bool) |
| } |
| x.types[t] = true |
| } |
| return true |
| }) |
| } |
| |
| // Print pretty-prints x to w. |
| func (x NamedTypes) Print(w io.Writer) { |
| var sorted typesByPkgAndName |
| for t, _ := range x.types { |
| sorted = append(sorted, t) |
| } |
| sort.Sort(sorted) |
| for _, t := range sorted { |
| path, name := vdl.SplitIdent(t.Name()) |
| fmt.Fprintf(w, "\ntype %s %s\n", qualifiedName(path, name), BaseType(t, "", nil)) |
| } |
| } |
| |
| // typesByPkgAndName orders types by package path, then by name. |
| type typesByPkgAndName []*vdl.Type |
| |
| func (x typesByPkgAndName) Len() int { return len(x) } |
| func (x typesByPkgAndName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } |
| func (x typesByPkgAndName) Less(i, j int) bool { |
| ipkg, iname := vdl.SplitIdent(x[i].Name()) |
| jpkg, jname := vdl.SplitIdent(x[j].Name()) |
| if ipkg != jpkg { |
| return ipkg < jpkg |
| } |
| return iname < jname |
| } |
| |
| // PrintInterface pretty-prints x to w. The types are used to collect named |
| // types, so that they may be printed later. |
| func PrintInterface(w io.Writer, x signature.Interface, types *NamedTypes) { |
| printDoc(w, x.Doc) |
| fmt.Fprintf(w, "type %s interface {", qualifiedName(x.PkgPath, x.Name)) |
| indentW := textutil.ByteReplaceWriter(w, '\n', "\n\t") |
| for _, embed := range x.Embeds { |
| fmt.Fprint(indentW, "\n") |
| PrintEmbed(indentW, embed) |
| } |
| for _, method := range x.Methods { |
| fmt.Fprint(indentW, "\n") |
| PrintMethod(indentW, method, types) |
| } |
| fmt.Fprintf(w, "\n}") |
| } |
| |
| // PrintEmbed pretty-prints x to w. |
| func PrintEmbed(w io.Writer, x signature.Embed) { |
| printDoc(w, x.Doc) |
| fmt.Fprint(w, qualifiedName(x.PkgPath, x.Name)) |
| } |
| |
| // PrintMethod pretty-prints x to w. The types are used to collect named types, |
| // so that they may be printed later. |
| func PrintMethod(w io.Writer, x signature.Method, types *NamedTypes) { |
| printDoc(w, x.Doc) |
| fmt.Fprintf(w, "%s(%s)%s%s%s", x.Name, inArgsStr(x.InArgs, types), streamArgsStr(x.InStream, x.OutStream, types), outArgsStr(x.OutArgs, types), tagsStr(x.Tags, types)) |
| } |
| |
| func inArgsStr(args []signature.Arg, types *NamedTypes) string { |
| var ret []string |
| for _, arg := range args { |
| ret = append(ret, argStr(arg, types)) |
| } |
| return strings.Join(ret, ", ") |
| } |
| |
| func outArgsStr(args []signature.Arg, types *NamedTypes) string { |
| if len(args) == 0 { |
| return " error" |
| } |
| out := make([]string, len(args)) |
| for i, arg := range args { |
| out[i] = argStr(arg, types) |
| } |
| return fmt.Sprintf(" (%s | error)", strings.Join(out, ", ")) |
| } |
| |
| func argStr(arg signature.Arg, types *NamedTypes) string { |
| // TODO(toddw): Print arg.Doc somewhere? |
| var ret string |
| if arg.Name != "" { |
| ret += arg.Name + " " |
| } |
| types.Add(arg.Type) |
| return ret + Type(arg.Type, "", nil) |
| } |
| |
| func streamArgsStr(in, out *signature.Arg, types *NamedTypes) string { |
| if in == nil && out == nil { |
| return "" |
| } |
| return fmt.Sprintf(" stream<%s, %s>", streamArgStr(in, types), streamArgStr(out, types)) |
| } |
| |
| func streamArgStr(arg *signature.Arg, types *NamedTypes) string { |
| // TODO(toddw): Print arg.Name and arg.Doc? |
| if arg == nil { |
| return "_" |
| } |
| types.Add(arg.Type) |
| return Type(arg.Type, "", nil) |
| } |
| |
| func tagsStr(tags []*vdl.Value, types *NamedTypes) string { |
| if len(tags) == 0 { |
| return "" |
| } |
| var ret []string |
| for _, tag := range tags { |
| types.Add(tag.Type()) |
| ret = append(ret, TypedConst(tag, "", nil)) |
| } |
| return fmt.Sprintf(" {%s}", strings.Join(ret, ", ")) |
| } |
| |
| func qualifiedName(pkgpath, name string) string { |
| if pkgpath == "" { |
| if name == "" { |
| return "<empty>" |
| } |
| return name |
| } |
| return fmt.Sprintf("%q.%s", pkgpath, name) |
| } |
| |
| func printDoc(w io.Writer, doc string) { |
| if doc == "" { |
| return |
| } |
| // TODO(toddw): Normalize the doc strings so that it never has the // or /**/ |
| // comment markers, and add them consistently here. And use LineWriter to |
| // pretty-print the doc. |
| if !strings.HasPrefix(doc, "//") { |
| fmt.Fprintln(w, "// "+doc) |
| } else { |
| fmt.Fprintln(w, doc) |
| } |
| } |