github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/pkg/gen/gen.go (about)

     1  // Copyright 2018 The CUE Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package gen is a command that can be used to bootstrap a new builtin package
    16  // directory. The directory has to reside in github.com/joomcode/cue/pkg.
    17  //
    18  // To bootstrap a directory, run this command from within that direcory.
    19  // After that directory's files can be regenerated with go generate.
    20  //
    21  // Be sure to also update an entry in pkg/pkg.go, if so desired.
    22  package main
    23  
    24  import (
    25  	"bytes"
    26  	"flag"
    27  	"fmt"
    28  	"go/ast"
    29  	"go/constant"
    30  	"go/format"
    31  	"go/parser"
    32  	"go/printer"
    33  	"go/token"
    34  	"io"
    35  	"io/ioutil"
    36  	"log"
    37  	"math/big"
    38  	"os"
    39  	"path"
    40  	"path/filepath"
    41  	"strings"
    42  
    43  	"github.com/joomcode/cue/cue"
    44  	"github.com/joomcode/cue/cue/errors"
    45  	cueformat "github.com/joomcode/cue/cue/format"
    46  	"github.com/joomcode/cue/cue/load"
    47  	"github.com/joomcode/cue/internal"
    48  )
    49  
    50  const genFile = "pkg.go"
    51  
    52  const prefix = "../pkg/"
    53  
    54  const header = `// Code generated by go generate. DO NOT EDIT.
    55  
    56   //go:generate rm %s
    57   //go:generate go run %sgen/gen.go
    58  
    59  package %s
    60  
    61  import (
    62  	"github.com/joomcode/cue/internal/core/adt"
    63  	"github.com/joomcode/cue/pkg/internal"
    64  )
    65  
    66  func init() {
    67  	internal.Register(%q, pkg)
    68  }
    69  
    70  var _ = adt.TopKind // in case the adt package isn't used
    71  
    72  `
    73  
    74  func main() {
    75  	flag.Parse()
    76  	log.SetFlags(log.Lshortfile)
    77  	log.SetOutput(os.Stdout)
    78  
    79  	g := generator{
    80  		w:     &bytes.Buffer{},
    81  		decls: &bytes.Buffer{},
    82  		fset:  token.NewFileSet(),
    83  	}
    84  
    85  	cwd, _ := os.Getwd()
    86  	pkg := strings.Split(filepath.ToSlash(cwd), "/pkg/")[1]
    87  	gopkg := path.Base(pkg)
    88  	// TODO: rename list to lists and struct to structs.
    89  	if gopkg == "struct" {
    90  		gopkg = "structs"
    91  	}
    92  	dots := strings.Repeat("../", strings.Count(pkg, "/")+1)
    93  
    94  	w := &bytes.Buffer{}
    95  	fmt.Fprintf(w, header, genFile, dots, gopkg, pkg)
    96  	g.processDir(pkg)
    97  
    98  	io.Copy(w, g.decls)
    99  	io.Copy(w, g.w)
   100  
   101  	b, err := format.Source(w.Bytes())
   102  	if err != nil {
   103  		b = w.Bytes() // write the unformatted source
   104  	}
   105  
   106  	b = bytes.Replace(b, []byte(".Builtin{{}}"), []byte(".Builtin{}"), -1)
   107  
   108  	filename := filepath.Join(genFile)
   109  
   110  	if err := ioutil.WriteFile(filename, b, 0666); err != nil {
   111  		log.Fatal(err)
   112  	}
   113  }
   114  
   115  type generator struct {
   116  	w          *bytes.Buffer
   117  	decls      *bytes.Buffer
   118  	name       string
   119  	fset       *token.FileSet
   120  	defaultPkg string
   121  	first      bool
   122  	iota       int
   123  
   124  	imports []*ast.ImportSpec
   125  }
   126  
   127  func (g *generator) processDir(pkg string) {
   128  	goFiles, err := filepath.Glob("*.go")
   129  	if err != nil {
   130  		log.Fatal(err)
   131  	}
   132  
   133  	cueFiles, err := filepath.Glob("*.cue")
   134  	if err != nil {
   135  		log.Fatal(err)
   136  	}
   137  
   138  	if len(goFiles)+len(cueFiles) == 0 {
   139  		return
   140  	}
   141  
   142  	fmt.Fprintf(g.w, "var pkg = &internal.Package{\nNative: []*internal.Builtin{{\n")
   143  	g.first = true
   144  	for _, filename := range goFiles {
   145  		if filename == genFile {
   146  			continue
   147  		}
   148  		g.processGo(filename)
   149  	}
   150  	fmt.Fprintf(g.w, "}},\n")
   151  	g.processCUE(pkg)
   152  	fmt.Fprintf(g.w, "}\n")
   153  }
   154  
   155  func (g *generator) sep() {
   156  	if g.first {
   157  		g.first = false
   158  		return
   159  	}
   160  	fmt.Fprintln(g.w, "}, {")
   161  }
   162  
   163  // processCUE mixes in CUE definitions defined in the package directory.
   164  func (g *generator) processCUE(pkg string) {
   165  	instances := cue.Build(load.Instances([]string{"."}, &load.Config{
   166  		StdRoot: ".",
   167  	}))
   168  
   169  	if err := instances[0].Err; err != nil {
   170  		if !strings.Contains(err.Error(), "no CUE files") {
   171  			errors.Print(os.Stderr, err, nil)
   172  			log.Fatalf("error processing %s: %v", pkg, err)
   173  		}
   174  		return
   175  	}
   176  
   177  	v := instances[0].Value().Syntax(cue.Raw())
   178  	// fmt.Printf("%T\n", v)
   179  	// fmt.Println(astinternal.DebugStr(v))
   180  	n := internal.ToExpr(v)
   181  	b, err := cueformat.Node(n)
   182  	if err != nil {
   183  		log.Fatal(err)
   184  	}
   185  	b = bytes.ReplaceAll(b, []byte("\n\n"), []byte("\n"))
   186  	// body = strings.ReplaceAll(body, "\t", "")
   187  	// TODO: escape backtick
   188  	fmt.Fprintf(g.w, "CUE: `%s`,\n", string(b))
   189  }
   190  
   191  func (g *generator) processGo(filename string) {
   192  	if strings.HasSuffix(filename, "_test.go") {
   193  		return
   194  	}
   195  	f, err := parser.ParseFile(g.fset, filename, nil, parser.ParseComments)
   196  	if err != nil {
   197  		log.Fatal(err)
   198  	}
   199  	g.defaultPkg = ""
   200  	g.name = f.Name.Name
   201  	if g.name == "structs" {
   202  		g.name = "struct"
   203  	}
   204  
   205  	for _, d := range f.Decls {
   206  		switch x := d.(type) {
   207  		case *ast.GenDecl:
   208  			switch x.Tok {
   209  			case token.CONST:
   210  				for _, spec := range x.Specs {
   211  					if !ast.IsExported(spec.(*ast.ValueSpec).Names[0].Name) {
   212  						continue
   213  					}
   214  					g.genConst(spec.(*ast.ValueSpec))
   215  				}
   216  			case token.VAR:
   217  				continue
   218  			case token.TYPE:
   219  				// TODO: support type declarations.
   220  				continue
   221  			case token.IMPORT:
   222  				continue
   223  			default:
   224  				log.Fatalf("gen %s: unexpected spec of type %s", filename, x.Tok)
   225  			}
   226  		case *ast.FuncDecl:
   227  			g.genFun(x)
   228  		}
   229  	}
   230  }
   231  
   232  func (g *generator) genConst(spec *ast.ValueSpec) {
   233  	name := spec.Names[0].Name
   234  	value := ""
   235  	switch v := g.toValue(spec.Values[0]); v.Kind() {
   236  	case constant.Bool, constant.Int, constant.String:
   237  		// TODO: convert octal numbers
   238  		value = v.ExactString()
   239  	case constant.Float:
   240  		var rat big.Rat
   241  		rat.SetString(v.ExactString())
   242  		var float big.Float
   243  		float.SetRat(&rat)
   244  		value = float.Text('g', -1)
   245  	default:
   246  		fmt.Printf("Dropped entry %s.%s (%T: %v)\n", g.defaultPkg, name, v.Kind(), v.ExactString())
   247  		return
   248  	}
   249  	g.sep()
   250  	fmt.Fprintf(g.w, "Name: %q,\n Const: %q,\n", name, value)
   251  }
   252  
   253  func (g *generator) toValue(x ast.Expr) constant.Value {
   254  	switch x := x.(type) {
   255  	case *ast.BasicLit:
   256  		return constant.MakeFromLiteral(x.Value, x.Kind, 0)
   257  	case *ast.BinaryExpr:
   258  		return constant.BinaryOp(g.toValue(x.X), x.Op, g.toValue(x.Y))
   259  	case *ast.UnaryExpr:
   260  		return constant.UnaryOp(x.Op, g.toValue(x.X), 0)
   261  	default:
   262  		log.Fatalf("%s: unsupported expression type %T: %#v", g.defaultPkg, x, x)
   263  	}
   264  	return constant.MakeUnknown()
   265  }
   266  
   267  func (g *generator) genFun(x *ast.FuncDecl) {
   268  	if x.Body == nil || !ast.IsExported(x.Name.Name) {
   269  		return
   270  	}
   271  	types := []string{}
   272  	if x.Type.Results != nil {
   273  		for _, f := range x.Type.Results.List {
   274  			if len(f.Names) > 0 {
   275  				for range f.Names {
   276  					types = append(types, g.goKind(f.Type))
   277  				}
   278  			} else {
   279  				types = append(types, g.goKind(f.Type))
   280  			}
   281  		}
   282  	}
   283  	if n := len(types); n != 1 && (n != 2 || types[1] != "error") {
   284  		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)
   285  		return
   286  	}
   287  
   288  	if x.Recv != nil {
   289  		// if strings.HasPrefix(x.Name.Name, g.name) {
   290  		// 	printer.Fprint(g.decls, g.fset, x)
   291  		// 	fmt.Fprint(g.decls, "\n\n")
   292  		// }
   293  		return
   294  	}
   295  
   296  	g.sep()
   297  	fmt.Fprintf(g.w, "Name: %q,\n", x.Name.Name)
   298  
   299  	args := []string{}
   300  	vals := []string{}
   301  	kind := []string{}
   302  	for _, f := range x.Type.Params.List {
   303  		for _, name := range f.Names {
   304  			typ := strings.Title(g.goKind(f.Type))
   305  			argKind := g.goToCUE(f.Type)
   306  			vals = append(vals, fmt.Sprintf("c.%s(%d)", typ, len(args)))
   307  			args = append(args, name.Name)
   308  			kind = append(kind, argKind)
   309  		}
   310  	}
   311  
   312  	fmt.Fprintf(g.w, "Params: []internal.Param{\n")
   313  	for _, k := range kind {
   314  		fmt.Fprintf(g.w, "{Kind: %s},\n", k)
   315  	}
   316  	fmt.Fprintf(g.w, "\n},\n")
   317  
   318  	expr := x.Type.Results.List[0].Type
   319  	fmt.Fprintf(g.w, "Result: %s,\n", g.goToCUE(expr))
   320  
   321  	argList := strings.Join(args, ", ")
   322  	valList := strings.Join(vals, ", ")
   323  	init := ""
   324  	if len(args) > 0 {
   325  		init = fmt.Sprintf("%s := %s", argList, valList)
   326  	}
   327  
   328  	fmt.Fprintf(g.w, "Func: func(c *internal.CallCtxt) {")
   329  	defer fmt.Fprintln(g.w, "},")
   330  	fmt.Fprintln(g.w)
   331  	if init != "" {
   332  		fmt.Fprintln(g.w, init)
   333  	}
   334  	fmt.Fprintln(g.w, "if c.Do() {")
   335  	defer fmt.Fprintln(g.w, "}")
   336  	if len(types) == 1 {
   337  		fmt.Fprintf(g.w, "c.Ret = %s(%s)", x.Name.Name, argList)
   338  	} else {
   339  		fmt.Fprintf(g.w, "c.Ret, c.Err = %s(%s)", x.Name.Name, argList)
   340  	}
   341  }
   342  
   343  func (g *generator) goKind(expr ast.Expr) string {
   344  	if star, isStar := expr.(*ast.StarExpr); isStar {
   345  		expr = star.X
   346  	}
   347  	w := &bytes.Buffer{}
   348  	printer.Fprint(w, g.fset, expr)
   349  	switch str := w.String(); str {
   350  	case "big.Int":
   351  		return "bigInt"
   352  	case "big.Float":
   353  		return "bigFloat"
   354  	case "big.Rat":
   355  		return "bigRat"
   356  	case "adt.Bottom":
   357  		return "error"
   358  	case "internal.Decimal":
   359  		return "decimal"
   360  	case "[]*internal.Decimal":
   361  		return "decimalList"
   362  	case "cue.Struct":
   363  		return "struct"
   364  	case "cue.Value":
   365  		return "value"
   366  	case "cue.List":
   367  		return "list"
   368  	case "[]string":
   369  		return "stringList"
   370  	case "[]byte":
   371  		return "bytes"
   372  	case "[]cue.Value":
   373  		return "list"
   374  	case "io.Reader":
   375  		return "reader"
   376  	case "time.Time":
   377  		return "string"
   378  	default:
   379  		return str
   380  	}
   381  }
   382  
   383  func (g *generator) goToCUE(expr ast.Expr) (cueKind string) {
   384  	// TODO: detect list and structs types for return values.
   385  	switch k := g.goKind(expr); k {
   386  	case "error":
   387  		cueKind += "adt.BottomKind"
   388  	case "bool":
   389  		cueKind += "adt.BoolKind"
   390  	case "bytes", "reader":
   391  		cueKind += "adt.BytesKind|adt.StringKind"
   392  	case "string":
   393  		cueKind += "adt.StringKind"
   394  	case "int", "int8", "int16", "int32", "rune", "int64",
   395  		"uint", "byte", "uint8", "uint16", "uint32", "uint64",
   396  		"bigInt":
   397  		cueKind += "adt.IntKind"
   398  	case "float64", "bigRat", "bigFloat", "decimal":
   399  		cueKind += "adt.NumKind"
   400  	case "list", "decimalList", "stringList":
   401  		cueKind += "adt.ListKind"
   402  	case "struct":
   403  		cueKind += "adt.StructKind"
   404  	case "value":
   405  		// Must use callCtxt.value method for these types and resolve manually.
   406  		cueKind += "adt.TopKind" // TODO: can be more precise
   407  	default:
   408  		switch {
   409  		case strings.HasPrefix(k, "[]"):
   410  			cueKind += "adt.ListKind"
   411  		case strings.HasPrefix(k, "map["):
   412  			cueKind += "adt.StructKind"
   413  		default:
   414  			// log.Println("Unknown type:", k)
   415  			// Must use callCtxt.value method for these types and resolve manually.
   416  			cueKind += "adt.TopKind" // TODO: can be more precise
   417  		}
   418  	}
   419  	return cueKind
   420  }