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 }