| // Copyright 2018 The CUE Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Package gen is a command that can be used to bootstrap a new builtin package |
| // directory. The directory has to reside in cuelang.org/go/pkg. |
| // |
| // To bootstrap a directory, run this command from within that direcory. |
| // After that directory's files can be regenerated with go generate. |
| // |
| // Be sure to also update an entry in pkg/pkg.go, if so desired. |
| package main |
| |
| import ( |
| "bytes" |
| "flag" |
| "fmt" |
| "go/ast" |
| "go/constant" |
| "go/format" |
| "go/parser" |
| "go/printer" |
| "go/token" |
| "io" |
| "io/ioutil" |
| "log" |
| "math/big" |
| "os" |
| "path" |
| "path/filepath" |
| "strings" |
| |
| "cuelang.org/go/cue" |
| "cuelang.org/go/cue/errors" |
| cueformat "cuelang.org/go/cue/format" |
| "cuelang.org/go/cue/load" |
| "cuelang.org/go/internal" |
| ) |
| |
| const genFile = "pkg.go" |
| |
| const prefix = "../pkg/" |
| |
| const header = `// Code generated by go generate. DO NOT EDIT. |
| |
| //go:generate rm %s |
| //go:generate go run %sgen/gen.go |
| |
| package %s |
| |
| import ( |
| "cuelang.org/go/internal/core/adt" |
| "cuelang.org/go/pkg/internal" |
| ) |
| |
| func init() { |
| internal.Register(%q, pkg) |
| } |
| |
| var _ = adt.TopKind // in case the adt package isn't used |
| |
| ` |
| |
| func main() { |
| flag.Parse() |
| log.SetFlags(log.Lshortfile) |
| log.SetOutput(os.Stdout) |
| |
| g := generator{ |
| w: &bytes.Buffer{}, |
| decls: &bytes.Buffer{}, |
| fset: token.NewFileSet(), |
| } |
| |
| cwd, _ := os.Getwd() |
| pkg := strings.Split(filepath.ToSlash(cwd), "/pkg/")[1] |
| gopkg := path.Base(pkg) |
| // TODO: rename list to lists and struct to structs. |
| if gopkg == "struct" { |
| gopkg = "structs" |
| } |
| dots := strings.Repeat("../", strings.Count(pkg, "/")+1) |
| |
| w := &bytes.Buffer{} |
| fmt.Fprintf(w, header, genFile, dots, gopkg, pkg) |
| g.processDir(pkg) |
| |
| io.Copy(w, g.decls) |
| io.Copy(w, g.w) |
| |
| b, err := format.Source(w.Bytes()) |
| if err != nil { |
| b = w.Bytes() // write the unformatted source |
| } |
| |
| b = bytes.Replace(b, []byte(".Builtin{{}}"), []byte(".Builtin{}"), -1) |
| |
| filename := filepath.Join(genFile) |
| |
| if err := ioutil.WriteFile(filename, b, 0644); err != nil { |
| log.Fatal(err) |
| } |
| } |
| |
| type generator struct { |
| w *bytes.Buffer |
| decls *bytes.Buffer |
| name string |
| fset *token.FileSet |
| defaultPkg string |
| first bool |
| iota int |
| |
| imports []*ast.ImportSpec |
| } |
| |
| func (g *generator) processDir(pkg string) { |
| goFiles, err := filepath.Glob("*.go") |
| if err != nil { |
| log.Fatal(err) |
| } |
| |
| cueFiles, err := filepath.Glob("*.cue") |
| if err != nil { |
| log.Fatal(err) |
| } |
| |
| if len(goFiles)+len(cueFiles) == 0 { |
| return |
| } |
| |
| fmt.Fprintf(g.w, "var pkg = &internal.Package{\nNative: []*internal.Builtin{{\n") |
| g.first = true |
| for _, filename := range goFiles { |
| if filename == genFile { |
| continue |
| } |
| g.processGo(filename) |
| } |
| fmt.Fprintf(g.w, "}},\n") |
| g.processCUE(pkg) |
| fmt.Fprintf(g.w, "}\n") |
| } |
| |
| func (g *generator) sep() { |
| if g.first { |
| g.first = false |
| return |
| } |
| fmt.Fprintln(g.w, "}, {") |
| } |
| |
| // processCUE mixes in CUE definitions defined in the package directory. |
| func (g *generator) processCUE(pkg string) { |
| instances := cue.Build(load.Instances([]string{"."}, &load.Config{ |
| StdRoot: ".", |
| })) |
| |
| if err := instances[0].Err; err != nil { |
| if !strings.Contains(err.Error(), "no CUE files") { |
| errors.Print(os.Stderr, err, nil) |
| log.Fatalf("error processing %s: %v", pkg, err) |
| } |
| return |
| } |
| |
| v := instances[0].Value().Syntax(cue.Raw()) |
| // fmt.Printf("%T\n", v) |
| // fmt.Println(internal.DebugStr(v)) |
| n := internal.ToExpr(v) |
| b, err := cueformat.Node(n) |
| if err != nil { |
| log.Fatal(err) |
| } |
| b = bytes.ReplaceAll(b, []byte("\n\n"), []byte("\n")) |
| // body = strings.ReplaceAll(body, "\t", "") |
| // TODO: escape backtick |
| fmt.Fprintf(g.w, "CUE: `%s`,\n", string(b)) |
| } |
| |
| func (g *generator) processGo(filename string) { |
| if strings.HasSuffix(filename, "_test.go") { |
| return |
| } |
| f, err := parser.ParseFile(g.fset, filename, nil, parser.ParseComments) |
| if err != nil { |
| log.Fatal(err) |
| } |
| g.defaultPkg = "" |
| g.name = f.Name.Name |
| if g.name == "structs" { |
| g.name = "struct" |
| } |
| |
| for _, d := range f.Decls { |
| switch x := d.(type) { |
| case *ast.GenDecl: |
| switch x.Tok { |
| case token.CONST: |
| for _, spec := range x.Specs { |
| if !ast.IsExported(spec.(*ast.ValueSpec).Names[0].Name) { |
| continue |
| } |
| g.genConst(spec.(*ast.ValueSpec)) |
| } |
| case token.VAR: |
| continue |
| case token.TYPE: |
| // TODO: support type declarations. |
| continue |
| case token.IMPORT: |
| continue |
| default: |
| log.Fatalf("gen %s: unexpected spec of type %s", filename, x.Tok) |
| } |
| case *ast.FuncDecl: |
| g.genFun(x) |
| } |
| } |
| } |
| |
| func (g *generator) genConst(spec *ast.ValueSpec) { |
| name := spec.Names[0].Name |
| value := "" |
| switch v := g.toValue(spec.Values[0]); v.Kind() { |
| case constant.Bool, constant.Int, constant.String: |
| // TODO: convert octal numbers |
| value = v.ExactString() |
| case constant.Float: |
| var rat big.Rat |
| rat.SetString(v.ExactString()) |
| var float big.Float |
| float.SetRat(&rat) |
| value = float.Text('g', -1) |
| default: |
| fmt.Printf("Dropped entry %s.%s (%T: %v)\n", g.defaultPkg, name, v.Kind(), v.ExactString()) |
| return |
| } |
| g.sep() |
| fmt.Fprintf(g.w, "Name: %q,\n Const: %q,\n", name, value) |
| } |
| |
| func (g *generator) toValue(x ast.Expr) constant.Value { |
| switch x := x.(type) { |
| case *ast.BasicLit: |
| return constant.MakeFromLiteral(x.Value, x.Kind, 0) |
| case *ast.BinaryExpr: |
| return constant.BinaryOp(g.toValue(x.X), x.Op, g.toValue(x.Y)) |
| case *ast.UnaryExpr: |
| return constant.UnaryOp(x.Op, g.toValue(x.X), 0) |
| default: |
| log.Fatalf("%s: unsupported expression type %T: %#v", g.defaultPkg, x, x) |
| } |
| return constant.MakeUnknown() |
| } |
| |
| func (g *generator) genFun(x *ast.FuncDecl) { |
| if x.Body == nil || !ast.IsExported(x.Name.Name) { |
| return |
| } |
| types := []string{} |
| if x.Type.Results != nil { |
| for _, f := range x.Type.Results.List { |
| if len(f.Names) > 0 { |
| for range f.Names { |
| types = append(types, g.goKind(f.Type)) |
| } |
| } else { |
| types = append(types, g.goKind(f.Type)) |
| } |
| } |
| } |
| if n := len(types); n != 1 && (n != 2 || types[1] != "error") { |
| fmt.Printf("Dropped func %s.%s: must have one return value or a value and an error %v\n", g.defaultPkg, x.Name.Name, types) |
| return |
| } |
| |
| if x.Recv != nil { |
| // if strings.HasPrefix(x.Name.Name, g.name) { |
| // printer.Fprint(g.decls, g.fset, x) |
| // fmt.Fprint(g.decls, "\n\n") |
| // } |
| return |
| } |
| |
| g.sep() |
| fmt.Fprintf(g.w, "Name: %q,\n", x.Name.Name) |
| |
| args := []string{} |
| vals := []string{} |
| kind := []string{} |
| for _, f := range x.Type.Params.List { |
| for _, name := range f.Names { |
| typ := strings.Title(g.goKind(f.Type)) |
| argKind := g.goToCUE(f.Type) |
| vals = append(vals, fmt.Sprintf("c.%s(%d)", typ, len(args))) |
| args = append(args, name.Name) |
| kind = append(kind, argKind) |
| } |
| } |
| |
| fmt.Fprintf(g.w, "Params: []adt.Kind{%s},\n", strings.Join(kind, ", ")) |
| result := g.goToCUE(x.Type.Results.List[0].Type) |
| fmt.Fprintf(g.w, "Result: %s,\n", result) |
| argList := strings.Join(args, ", ") |
| valList := strings.Join(vals, ", ") |
| init := "" |
| if len(args) > 0 { |
| init = fmt.Sprintf("%s := %s", argList, valList) |
| } |
| |
| fmt.Fprintf(g.w, "Func: func(c *internal.CallCtxt) {") |
| defer fmt.Fprintln(g.w, "},") |
| fmt.Fprintln(g.w) |
| if init != "" { |
| fmt.Fprintln(g.w, init) |
| } |
| fmt.Fprintln(g.w, "if c.Do() {") |
| defer fmt.Fprintln(g.w, "}") |
| if len(types) == 1 { |
| fmt.Fprintf(g.w, "c.Ret = %s(%s)", x.Name.Name, argList) |
| } else { |
| fmt.Fprintf(g.w, "c.Ret, c.Err = %s(%s)", x.Name.Name, argList) |
| } |
| } |
| |
| func (g *generator) goKind(expr ast.Expr) string { |
| if star, isStar := expr.(*ast.StarExpr); isStar { |
| expr = star.X |
| } |
| w := &bytes.Buffer{} |
| printer.Fprint(w, g.fset, expr) |
| switch str := w.String(); str { |
| case "big.Int": |
| return "bigInt" |
| case "big.Float": |
| return "bigFloat" |
| case "big.Rat": |
| return "bigRat" |
| case "internal.Decimal": |
| return "decimal" |
| case "[]*internal.Decimal": |
| return "decimalList" |
| case "cue.Struct": |
| return "struct" |
| case "cue.Value": |
| return "value" |
| case "cue.List": |
| return "list" |
| case "[]string": |
| return "stringList" |
| case "[]byte": |
| return "bytes" |
| case "[]cue.Value": |
| return "list" |
| case "io.Reader": |
| return "reader" |
| case "time.Time": |
| return "string" |
| default: |
| return str |
| } |
| } |
| |
| func (g *generator) goToCUE(expr ast.Expr) (cueKind string) { |
| // TODO: detect list and structs types for return values. |
| switch k := g.goKind(expr); k { |
| case "error": |
| cueKind += "adt.BottomKind" |
| case "bool": |
| cueKind += "adt.BoolKind" |
| case "bytes", "reader": |
| cueKind += "adt.BytesKind|adt.StringKind" |
| case "string": |
| cueKind += "adt.StringKind" |
| case "int", "int8", "int16", "int32", "rune", "int64", |
| "uint", "byte", "uint8", "uint16", "uint32", "uint64", |
| "bigInt": |
| cueKind += "adt.IntKind" |
| case "float64", "bigRat", "bigFloat", "decimal": |
| cueKind += "adt.NumKind" |
| case "list", "decimalList", "stringList": |
| cueKind += "adt.ListKind" |
| case "struct": |
| cueKind += "adt.StructKind" |
| case "value": |
| // Must use callCtxt.value method for these types and resolve manually. |
| cueKind += "adt.TopKind" // TODO: can be more precise |
| default: |
| switch { |
| case strings.HasPrefix(k, "[]"): |
| cueKind += "adt.ListKind" |
| case strings.HasPrefix(k, "map["): |
| cueKind += "adt.StructKind" |
| default: |
| // log.Println("Unknown type:", k) |
| // Must use callCtxt.value method for these types and resolve manually. |
| cueKind += "adt.TopKind" // TODO: can be more precise |
| } |
| } |
| return cueKind |
| } |