cmd/cue/cmd: move get to to use the ast
Using the AST makes it easier to to resolve
Issue #228.
Change-Id: Idec116806aa80fc1ea283911759a391059682571
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/4384
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cmd/cue/cmd/get_go.go b/cmd/cue/cmd/get_go.go
index 9945f90..b3de7a1 100644
--- a/cmd/cue/cmd/get_go.go
+++ b/cmd/cue/cmd/get_go.go
@@ -251,13 +251,11 @@
usedPkgs map[string]bool
// per file
- w *bytes.Buffer
cmap ast.CommentMap
pkg *packages.Package
consts map[string][]string
pkgNames map[string]pkgInfo
usedInFile map[string]bool
- indent int
exclusions []*regexp.Regexp
exclude string
@@ -408,8 +406,6 @@
}
for i, f := range p.Syntax {
- e.w = &bytes.Buffer{}
-
e.cmap = ast.NewCommentMap(p.Fset, f, f.Comments)
e.pkgNames = map[string]pkgInfo{}
@@ -431,17 +427,15 @@
e.pkgNames[pkgPath] = info
}
- hasEntries := false
+ decls := []cueast.Decl{}
for _, d := range f.Decls {
switch x := d.(type) {
case *ast.GenDecl:
- if e.reportDecl(e.w, x) {
- hasEntries = true
- }
+ decls = append(decls, e.reportDecl(x)...)
}
}
- if !hasEntries && f.Doc == nil {
+ if len(decls) == 0 && f.Doc == nil {
continue
}
@@ -451,43 +445,38 @@
}
sort.Strings(pkgs)
- w := &bytes.Buffer{}
+ pkg := &cueast.Package{Name: e.ident(p.Name)}
+ addDoc(f.Doc, pkg)
- fmt.Fprintln(w, "// Code generated by cue get go. DO NOT EDIT.")
- fmt.Fprintln(w)
- fmt.Fprintln(w, "//cue:generate cue get go", args)
- fmt.Fprintln(w)
- if f.Doc != nil {
- writeDoc(w, f.Doc)
- }
- fmt.Fprintf(w, "package %s\n", p.Name)
- fmt.Fprintln(w)
+ f := &cueast.File{Decls: []cueast.Decl{
+ internal.NewComment(false, "Code generated by cue get go. DO NOT EDIT."),
+ &cueast.CommentGroup{List: []*cueast.Comment{
+ {Text: "//cue:generate cue get go " + args},
+ }},
+ pkg,
+ }}
+
if len(pkgs) > 0 {
- fmt.Fprintln(w, "import (")
+ imports := &cueast.ImportDecl{}
+ f.Decls = append(f.Decls, imports)
for _, s := range pkgs {
info := e.pkgNames[s]
- if p.Imports[s].Name == info.name {
- fmt.Fprintf(w, "%q\n", info.id)
- } else {
- fmt.Fprintf(w, "%v %q\n", info.name, info.id)
+ spec := cueast.NewImport(nil, info.id)
+ if p.Imports[s].Name != info.name {
+ spec.Name = e.ident(info.name)
}
+ imports.Specs = append(imports.Specs, spec)
}
- fmt.Fprintln(w, ")")
- fmt.Fprintln(w)
}
- fmt.Fprintln(w)
- _, _ = io.Copy(w, e.w)
+
+ f.Decls = append(f.Decls, decls...)
file := filepath.Base(p.CompiledGoFiles[i])
file = strings.Replace(file, ".go", "_go", 1)
file += "_gen.cue"
- b, err := format.Source(w.Bytes())
+ b, err := format.Node(f)
if err != nil {
- _ = ioutil.WriteFile(filepath.Join(dir, file), w.Bytes(), 0644)
- stderr := e.cmd.Stderr()
- fmt.Fprintln(stderr, w.String())
- fmt.Fprintln(stderr, dir, file)
return err
}
err = ioutil.WriteFile(filepath.Join(dir, file), b, 0644)
@@ -568,7 +557,31 @@
}
}
-func (e *extractor) reportDecl(w io.Writer, x *ast.GenDecl) (added bool) {
+func (e *extractor) label(name string) cueast.Label {
+ if !cueast.IsValidIdent(name) {
+ return cueast.NewString(name)
+ }
+ return cueast.NewIdent(name)
+}
+
+func (e *extractor) ident(name string) *cueast.Ident {
+ return cueast.NewIdent(name)
+}
+
+func (e *extractor) def(doc *ast.CommentGroup, name string, value cueast.Expr, newline bool) *cueast.Field {
+ f := &cueast.Field{
+ Label: e.label(name),
+ Token: cuetoken.ISA,
+ Value: value,
+ }
+ addDoc(doc, f)
+ if newline {
+ cueast.SetRelPos(f, cuetoken.NewSection)
+ }
+ return f
+}
+
+func (e *extractor) reportDecl(x *ast.GenDecl) (a []cueast.Decl) {
switch x.Tok {
case token.TYPE:
for _, s := range x.Specs {
@@ -582,12 +595,10 @@
name := v.Name.Name
switch tn, ok := e.pkg.TypesInfo.Defs[v.Name].(*types.TypeName); {
case ok:
- if altType := e.altType(tn.Type()); altType != "" {
+ if altType := e.altType(tn.Type()); altType != nil {
// TODO: add the underlying tag as a Go tag once we have
// proper string escaping for CUE.
- e.printDoc(x.Doc, true)
- fmt.Fprintf(e.w, "%s :: %s", name, altType)
- added = true
+ a = append(a, e.def(x.Doc, name, altType, true))
break
}
fallthrough
@@ -597,40 +608,37 @@
e.logf(" Dropped declaration %v of unsupported type %v", name, typ)
continue
}
- added = true
-
- if s := e.altType(types.NewPointer(typ)); s != "" {
- e.printDoc(x.Doc, true)
- fmt.Fprint(e.w, name, " :: ", s)
+ if s := e.altType(types.NewPointer(typ)); s != nil {
+ a = append(a, e.def(x.Doc, name, s, true))
break
}
// TODO: only print original type if value is not marked as enum.
underlying := e.pkg.TypesInfo.TypeOf(v.Type)
- e.printField(name, cuetoken.ISA, underlying, x.Doc, true)
+ f, _ := e.makeField(name, cuetoken.ISA, underlying, x.Doc, true)
+ a = append(a, f)
+ cueast.SetRelPos(f, cuetoken.NewSection)
+
}
- e.indent++
if len(enums) > 0 {
- fmt.Fprintf(e.w, " // enum%s", name)
+ enumName := "enum" + name
+ a[len(a)-1].AddComment(internal.NewComment(false, enumName))
- e.newLine()
- e.newLine()
- fmt.Fprintf(e.w, "enum%s::\n%v", name, enums[0])
+ var x cueast.Expr = e.ident(enums[0])
+ cueast.SetRelPos(x, cuetoken.Newline)
for _, v := range enums[1:] {
- fmt.Fprint(e.w, " |")
- e.newLine()
- fmt.Fprint(e.w, v)
+ y := e.ident(v)
+ cueast.SetRelPos(y, cuetoken.Newline)
+ x = &cueast.BinaryExpr{X: x, Op: cuetoken.OR, Y: y}
}
+ a = append(a, e.def(nil, enumName, x, true))
}
- e.indent--
- e.newLine()
- e.newLine()
}
case token.CONST:
// TODO: copy over comments for constant blocks.
- for _, s := range x.Specs {
+ for k, s := range x.Specs {
// TODO: determine type name and filter.
v, ok := s.(*ast.ValueSpec)
if !ok {
@@ -641,58 +649,50 @@
if !ast.IsExported(name.Name) {
continue
}
- added = true
-
- e.printDoc(v.Doc, true)
- fmt.Fprint(e.w, name.Name, " :: ")
-
- typ := e.pkg.TypesInfo.TypeOf(name)
- if s := typ.String(); !strings.Contains(s, "untyped") {
- switch s {
- case "byte", "string", "error":
- default:
- e.printType(typ)
- fmt.Fprint(e.w, " & ")
- }
- }
+ f := e.def(v.Doc, name.Name, nil, k == 0)
+ a = append(a, f)
val := ""
- comment := ""
if i < len(v.Values) {
if lit, ok := v.Values[i].(*ast.BasicLit); ok {
val = lit.Value
}
}
- outer:
- switch {
- case len(val) <= 1:
- case val[0] == '\'':
- comment = " // " + val
- val = ""
+ c := e.pkg.TypesInfo.Defs[v.Names[i]].(*types.Const)
+ cv, err := parser.ParseExpr("", c.Val().String())
+ if err != nil {
+ panic(err)
+ }
- case strings.HasPrefix(val, "0"):
- for _, c := range val[1:] {
- if c < '0' || '9' < c {
- val = ""
- break outer
+ // Use orignal Go value if compatible with CUE (octal is okay)
+ if b, ok := cv.(*cueast.BasicLit); ok {
+ if b.Kind == cuetoken.INT && val != "" && val[0] != '\'' {
+ b.Value = val
+ }
+ if b.Value != val {
+ cv.AddComment(internal.NewComment(false, val))
+ }
+ }
+
+ typ := e.pkg.TypesInfo.TypeOf(name)
+ if s := typ.String(); !strings.Contains(s, "untyped") {
+ switch s {
+ case "byte", "string", "error":
+ default:
+ cv = &cueast.BinaryExpr{
+ X: e.makeType(typ),
+ Op: cuetoken.AND,
+ Y: cv,
}
}
- val = "0o" + val[1:]
}
- if val == "" {
- c := e.pkg.TypesInfo.Defs[v.Names[i]].(*types.Const)
- val = c.Val().String()
- }
-
- fmt.Fprint(e.w, val, comment)
- e.newLine()
+ f.Value = cv
}
}
- e.newLine()
}
- return added
+ return a
}
func shortTypeName(t types.Type) string {
@@ -702,14 +702,14 @@
return t.String()
}
-func (e *extractor) altType(typ types.Type) string {
+func (e *extractor) altType(typ types.Type) cueast.Expr {
ptr := types.NewPointer(typ)
for _, x := range toTop {
i := x.Underlying().(*types.Interface)
if types.Implements(typ, i) || types.Implements(ptr, i) {
t := shortTypeName(typ)
e.logf(" %v implements %s; setting type to _", t, x)
- return "_"
+ return e.ident("_")
}
}
for _, x := range toString {
@@ -717,16 +717,27 @@
if types.Implements(typ, i) || types.Implements(ptr, i) {
t := shortTypeName(typ)
e.logf(" %v implements %s; setting type to string", t, x)
- return "string"
+ return e.ident("string")
}
}
- return ""
+ return nil
}
-func writeDoc(w io.Writer, g *ast.CommentGroup) {
- if g == nil {
- return
+func addDoc(g *ast.CommentGroup, x cueast.Node) bool {
+ doc := makeDoc(g, true)
+ if doc != nil {
+ x.AddComment(doc)
+ return true
}
+ return false
+}
+
+func makeDoc(g *ast.CommentGroup, isDoc bool) *cueast.CommentGroup {
+ if g == nil {
+ return nil
+ }
+
+ a := []*cueast.Comment{}
for _, comment := range g.List {
c := comment.Text
@@ -736,7 +747,7 @@
switch c[1] {
case '/':
//-style comment (no newline at the end)
- _, _ = fmt.Fprintf(w, "//%s\n", c[2:])
+ a = append(a, &cueast.Comment{Text: c})
case '*':
/*-style comment */
@@ -773,28 +784,14 @@
// Print lines.
for _, l := range lines {
if i >= len(l) {
- _, _ = io.WriteString(w, "//\n")
+ a = append(a, &cueast.Comment{Text: "//"})
continue
}
- _, _ = fmt.Fprintf(w, "// %s\n", l[i:])
+ a = append(a, &cueast.Comment{Text: "// " + l[i:]})
}
}
}
-}
-
-func (e *extractor) printDoc(doc *ast.CommentGroup, newline bool) {
- if doc == nil {
- return
- }
- if newline {
- e.newLine()
- }
- writeDoc(e.w, doc)
-}
-
-func (e *extractor) newLine() {
- fmt.Fprintln(e.w)
- fmt.Fprint(e.w, strings.Repeat(" ", e.indent))
+ return &cueast.CommentGroup{Doc: isDoc, List: a}
}
func supportedType(stack []types.Type, t types.Type) (ok bool) {
@@ -867,27 +864,31 @@
return false
}
-func (e *extractor) printField(name string, kind cuetoken.Token, expr types.Type, doc *ast.CommentGroup, newline bool) (typename string) {
- e.printDoc(doc, newline)
- colon := ": "
- switch kind {
- case cuetoken.ISA:
- colon = " :: "
- case cuetoken.OPTION:
- colon = "?: "
+func (e *extractor) makeField(name string, kind cuetoken.Token, expr types.Type, doc *ast.CommentGroup, newline bool) (f *cueast.Field, typename string) {
+ typ := e.makeType(expr)
+ f = &cueast.Field{
+ Label: e.label(name),
+ Token: kind,
+ Value: typ,
}
- fmt.Fprint(e.w, name, colon)
- pos := e.w.Len()
- e.printType(expr)
- return e.w.String()[pos:]
+ if doc := makeDoc(doc, newline); doc != nil {
+ f.AddComment(doc)
+ cueast.SetRelPos(doc, cuetoken.NewSection)
+ }
+
+ if kind == cuetoken.OPTION {
+ f.Token = cuetoken.COLON
+ f.Optional = cuetoken.Blank.Pos()
+ }
+ b, _ := format.Node(typ)
+ return f, string(b)
}
-func (e *extractor) printType(expr types.Type) {
+func (e *extractor) makeType(expr types.Type) (result cueast.Expr) {
if x, ok := expr.(*types.Named); ok {
obj := x.Obj()
if obj.Pkg() == nil {
- fmt.Fprint(e.w, "_")
- return
+ return e.ident("_")
}
// Check for builtin packages.
// TODO: replace these literal types with a reference to the fixed
@@ -895,56 +896,55 @@
switch obj.Type().String() {
case "time.Time":
e.usedInFile["time"] = true
- fmt.Fprint(e.w, e.pkgNames[obj.Pkg().Path()].name, ".", obj.Name())
- return
+ ref := e.ident(e.pkgNames[obj.Pkg().Path()].name)
+ return cueast.NewSel(ref, obj.Name())
case "math/big.Int":
- fmt.Fprint(e.w, "int")
- return
+ return e.ident("int")
default:
if !strings.ContainsAny(obj.Pkg().Path(), ".") {
// Drop any standard library type if they haven't been handled
// above.
- if s := e.altType(obj.Type()); s != "" {
- fmt.Fprint(e.w, s)
- return
+ // TODO: Doc?
+ if s := e.altType(obj.Type()); s != nil {
+ return s
}
}
}
+ result = e.ident(obj.Name())
if pkg := obj.Pkg(); pkg != nil {
if info := e.pkgNames[pkg.Path()]; info.name != "" {
- fmt.Fprint(e.w, info.name, ".")
+ result = cueast.NewSel(e.ident(info.name), obj.Name())
e.usedPkg(pkg.Path())
}
}
- fmt.Fprint(e.w, obj.Name())
return
}
switch x := expr.(type) {
case *types.Pointer:
- fmt.Fprintf(e.w, "null | ")
- e.printType(x.Elem())
+ return &cueast.BinaryExpr{
+ X: e.ident("null"),
+ Op: cuetoken.OR,
+ Y: e.makeType(x.Elem()),
+ }
case *types.Struct:
- fmt.Fprint(e.w, "{")
- e.indent++
- e.printFields(x)
- e.indent--
- e.newLine()
- fmt.Fprint(e.w, "}")
+ st := &cueast.StructLit{
+ Lbrace: cuetoken.Blank.Pos(),
+ Rbrace: cuetoken.Newline.Pos(),
+ }
+ e.addFields(x, st)
+ return st
case *types.Slice:
// TODO: should this be x.Elem().Underlying().String()? One could
// argue either way.
if x.Elem().String() == "byte" {
- fmt.Fprint(e.w, "bytes")
- } else {
- fmt.Fprint(e.w, "[...")
- e.printType(x.Elem())
- fmt.Fprint(e.w, "]")
+ return e.ident("bytes")
}
+ return cueast.NewList(&cueast.Ellipsis{Type: e.makeType(x.Elem())})
case *types.Array:
if x.Elem().String() == "byte" {
@@ -953,26 +953,39 @@
// fmt.Fprint(e.w, fmt.Sprintf("=~ '^\C{%d}$'", x.Len())),
// but regexp does not support that.
// But translate to bytes, instead of [...byte] to be consistent.
- fmt.Fprint(e.w, "bytes")
+ return e.ident("bytes")
} else {
- fmt.Fprintf(e.w, "%d*[", x.Len())
- e.printType(x.Elem())
- fmt.Fprint(e.w, "]")
+ return &cueast.BinaryExpr{
+ X: &cueast.BasicLit{
+ Kind: cuetoken.INT,
+ Value: strconv.Itoa(int(x.Len())),
+ },
+ Op: cuetoken.MUL,
+ Y: cueast.NewList(e.makeType(x.Elem())),
+ }
}
case *types.Map:
if b, ok := x.Key().Underlying().(*types.Basic); !ok || b.Kind() != types.String {
panic(fmt.Sprintf("unsupported map key type %T", x.Key()))
}
- fmt.Fprintf(e.w, "{ [string]: ")
- e.printType(x.Elem())
- fmt.Fprintf(e.w, " }")
+
+ f := &cueast.Field{
+ Label: cueast.NewList(e.ident("string")),
+ Value: e.makeType(x.Elem()),
+ }
+ cueast.SetRelPos(f, cuetoken.Blank)
+ return &cueast.StructLit{
+ Lbrace: cuetoken.Blank.Pos(),
+ Elts: []cueast.Decl{f},
+ Rbrace: cuetoken.Blank.Pos(),
+ }
case *types.Basic:
- fmt.Fprint(e.w, x.String())
+ return e.ident(x.String())
case *types.Interface:
- fmt.Fprintf(e.w, "_")
+ return e.ident("_")
default:
// record error
@@ -980,7 +993,16 @@
}
}
-func (e *extractor) printFields(x *types.Struct) {
+func (e *extractor) addAttr(f *cueast.Field, tag, body string) {
+ s := fmt.Sprintf("@%s(%s)", tag, body)
+ f.Attrs = append(f.Attrs, &cueast.Attribute{Text: s})
+}
+
+func (e *extractor) addFields(x *types.Struct, st *cueast.StructLit) {
+ add := func(x cueast.Decl) {
+ st.Elts = append(st.Elts, x)
+ }
+
s := e.orig[x]
docs := []*ast.CommentGroup{}
for _, f := range s.Fields.List {
@@ -1005,19 +1027,15 @@
if f.Anonymous() && e.isInline(x.Tag(i)) {
typ := f.Type()
if _, ok := typ.(*types.Named); ok {
- // TODO: strongly consider allowing Expressions for embedded
- // values. This ties in with using dots instead of spaces,
- // comprehensions, and the ability to generate good
- // error messages, so thread carefully.
+ embed := &cueast.EmbedDecl{Expr: e.makeType(typ)}
if i > 0 {
- fmt.Fprintln(e.w)
+ cueast.SetRelPos(embed, cuetoken.NewSection)
}
- fmt.Fprint(e.w, "\n")
- e.printType(typ)
+ add(embed)
} else {
switch x := typ.(type) {
case *types.Struct:
- e.printFields(x)
+ e.addFields(x, st)
default:
panic(fmt.Sprintf("unimplemented embedding for type %T", x))
}
@@ -1029,10 +1047,7 @@
if name == "-" {
continue
}
- if x, _ := cueast.QuoteIdent(name); x != name {
- name = strconv.Quote(name)
- }
- e.newLine()
+ // TODO: check referrers
kind := cuetoken.COLON
if e.isOptional(tag) {
kind = cuetoken.OPTION
@@ -1040,7 +1055,8 @@
if _, ok := f.Type().(*types.Pointer); ok {
kind = cuetoken.OPTION
}
- cueType := e.printField(name, kind, f.Type(), docs[i], count > 0)
+ field, cueType := e.makeField(name, kind, f.Type(), docs[i], count > 0)
+ add(field)
// Add field tag to convert back to Go.
typeName := f.Type().String()
@@ -1052,17 +1068,17 @@
// TODO: remove fields in @go attr that are the same as printed?
if name != f.Name() || typeName != cueType {
- fmt.Fprint(e.w, "@go(")
+ buf := &strings.Builder{}
if name != f.Name() {
- fmt.Fprint(e.w, f.Name())
+ buf.WriteString(f.Name())
}
if typeName != cueType {
if strings.ContainsAny(typeName, `#"',()=`) {
typeName = strconv.Quote(typeName)
}
- fmt.Fprint(e.w, ",", typeName)
+ fmt.Fprint(buf, ",", typeName)
}
- fmt.Fprintf(e.w, ")")
+ e.addAttr(field, "go", buf.String())
}
// Carry over protobuf field tags with modifications.
@@ -1083,17 +1099,17 @@
if len(split) >= 2 {
split[0], split[1] = split[1], split[0]
}
- fmt.Fprintf(e.w, " @protobuf(%s)", strings.Join(split, ","))
+ e.addAttr(field, "protobuf", strings.Join(split, ","))
}
// Carry over XML tags.
if t := reflect.StructTag(tag).Get("xml"); t != "" {
- fmt.Fprintf(e.w, " @xml(%s)", t)
+ e.addAttr(field, "xml", t)
}
// Carry over TOML tags.
if t := reflect.StructTag(tag).Get("toml"); t != "" {
- fmt.Fprintf(e.w, " @toml(%s)", t)
+ e.addAttr(field, "toml", t)
}
// TODO: should we in general carry over any unknown tag verbatim?
diff --git a/cmd/cue/cmd/testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2/pkg2_go_gen.cue b/cmd/cue/cmd/testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2/pkg2_go_gen.cue
index 41dc9b9..79c99f6 100644
--- a/cmd/cue/cmd/testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2/pkg2_go_gen.cue
+++ b/cmd/cue/cmd/testdata/pkg/cuelang.org/go/cmd/cue/cmd/testdata/code/go/pkg2/pkg2_go_gen.cue
@@ -5,9 +5,7 @@
// Package pkgtwo does other stuff.
package pkgtwo
-import (
- t "time"
-)
+import t "time"
// A Barzer barzes.
Barzer :: {
diff --git a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/watch_go_gen.cue b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/watch_go_gen.cue
index 02b6b22..34a7ecd 100644
--- a/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/watch_go_gen.cue
+++ b/doc/tutorial/kubernetes/quick/cue.mod/gen/k8s.io/apimachinery/pkg/watch/watch_go_gen.cue
@@ -4,9 +4,7 @@
package watch
-import (
- "k8s.io/apimachinery/pkg/runtime"
-)
+import "k8s.io/apimachinery/pkg/runtime"
// Interface can be implemented by anything that knows how to watch and report changes.
Interface :: _
diff --git a/internal/internal.go b/internal/internal.go
index f4e0ab7..64a48e7 100644
--- a/internal/internal.go
+++ b/internal/internal.go
@@ -20,6 +20,9 @@
// TODO: refactor packages as to make this package unnecessary.
import (
+ "bufio"
+ "strings"
+
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/token"
"github.com/cockroachdb/apd/v2"
@@ -100,3 +103,25 @@
}
return nil, "", f.Pos()
}
+
+// NewComment creates a new CommentGroup from the given text.
+// Each line is prefixed with "//" and the last newline is removed.
+// Useful for ASTs generated by code other than the CUE parser.
+func NewComment(isDoc bool, s string) *ast.CommentGroup {
+ if s == "" {
+ return nil
+ }
+ cg := &ast.CommentGroup{Doc: isDoc}
+ if !isDoc {
+ cg.Line = true
+ cg.Position = 10
+ }
+ scanner := bufio.NewScanner(strings.NewReader(s))
+ for scanner.Scan() {
+ cg.List = append(cg.List, &ast.Comment{Text: "// " + scanner.Text()})
+ }
+ if last := len(cg.List) - 1; cg.List[last].Text == "// " {
+ cg.List = cg.List[:last]
+ }
+ return cg
+}