blob: f2b7832e42466da381209abf8f60245b4643e0ab [file] [log] [blame]
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +01001// 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// +build ignore
16
17package main
18
19import (
20 "bytes"
21 "flag"
22 "fmt"
23 "go/ast"
24 "go/constant"
25 "go/format"
26 "go/parser"
27 "go/printer"
28 "go/token"
29 "io"
30 "io/ioutil"
31 "log"
32 "math/big"
33 "os"
34 "path"
35 "path/filepath"
36 "sort"
37 "strconv"
38 "strings"
Marcel van Lohuizen57333362019-04-01 14:35:09 +020039
40 "cuelang.org/go/cue"
41 cueformat "cuelang.org/go/cue/format"
42 "cuelang.org/go/cue/load"
43 cuetoken "cuelang.org/go/cue/token"
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +010044)
45
46const prefix = "../pkg/"
47
48const header = `// Code generated by go generate. DO NOT EDIT.
49
50package cue
51
52`
53
54const initFunc = `
55func init() {
56 initBuiltins(builtinPackages)
57}
58
59var _ io.Reader
60`
61
62func main() {
63 flag.Parse()
64 log.SetFlags(log.Lshortfile)
65 log.SetOutput(os.Stdout)
66
67 g := generator{
68 w: &bytes.Buffer{},
69 decls: &bytes.Buffer{},
70 fset: token.NewFileSet(),
Marcel van Lohuizen57333362019-04-01 14:35:09 +020071 cfset: cuetoken.NewFileSet(),
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +010072 }
73
Marcel van Lohuizen57333362019-04-01 14:35:09 +020074 fmt.Fprintln(g.w, "var builtinPackages = map[string]*builtinPkg{")
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +010075 filepath.Walk(prefix, func(dir string, info os.FileInfo, err error) error {
76 if err != nil {
77 log.Fatal(err)
78 }
79 if info.Name() == "testdata" {
80 return filepath.SkipDir
81 }
82 if info.IsDir() {
83 g.processDir(dir)
84 }
85 return nil
86 })
87 fmt.Fprintln(g.w, "}")
88
89 w := &bytes.Buffer{}
90 fmt.Fprintln(w, header)
91
92 m := map[string]*ast.ImportSpec{}
93 keys := []string{}
94 for _, spec := range g.imports {
95 if spec.Path.Value == `"cuelang.org/go/cue"` {
96 // Don't add this package.
97 continue
98 }
99 if prev, ok := m[spec.Path.Value]; ok {
100 if importName(prev) != importName(spec) {
101 log.Fatalf("inconsistent name for import %s: %q != %q",
102 spec.Path.Value, importName(prev), importName(spec))
103 }
104 continue
105 }
106 m[spec.Path.Value] = spec
107 keys = append(keys, spec.Path.Value)
108 }
109 fmt.Fprintln(w, "import (")
110 sort.Strings(keys)
111 for _, k := range keys {
112 printer.Fprint(w, g.fset, m[k])
113 fmt.Fprintln(w)
114 }
115 fmt.Fprintln(w, ")")
116 fmt.Fprintln(w, initFunc)
117 io.Copy(w, g.decls)
118 io.Copy(w, g.w)
119
120 b, err := format.Source(w.Bytes())
121 if err != nil {
122 b = w.Bytes() // write the unformatted source
123 }
124 // TODO: do this in a more principled way. The best is probably to
125 // put all builtins in a separate package.
126 b = bytes.Replace(b, []byte("cue."), []byte(""), -1)
127
128 if err := ioutil.WriteFile("builtins.go", b, 0644); err != nil {
129 log.Fatal(err)
130 }
131 if err != nil {
132 log.Fatal(err)
133 }
134}
135
136type generator struct {
137 w *bytes.Buffer
138 decls *bytes.Buffer
139 fset *token.FileSet
Marcel van Lohuizen57333362019-04-01 14:35:09 +0200140 cfset *cuetoken.FileSet
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100141 defaultPkg string
142 first bool
143 iota int
144
145 imports []*ast.ImportSpec
146}
147
148func (g *generator) processDir(dir string) {
Marcel van Lohuizen57333362019-04-01 14:35:09 +0200149 goFiles, err := filepath.Glob(filepath.Join(dir, "*.go"))
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100150 if err != nil {
151 log.Fatal(err)
152 }
Marcel van Lohuizen57333362019-04-01 14:35:09 +0200153
154 cueFiles, err := filepath.Glob(filepath.Join(dir, "*.cue"))
155 if err != nil {
156 log.Fatal(err)
157 }
158
159 if len(goFiles)+len(cueFiles) == 0 {
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100160 return
161 }
Marcel van Lohuizen57333362019-04-01 14:35:09 +0200162
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100163 pkg := dir[len(prefix):]
Marcel van Lohuizen57333362019-04-01 14:35:09 +0200164 fmt.Fprintf(g.w, "%q: &builtinPkg{\nnative: []*builtin{{\n", pkg)
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100165 g.first = true
Marcel van Lohuizen57333362019-04-01 14:35:09 +0200166 for _, filename := range goFiles {
167 g.processGo(filename)
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100168 }
Marcel van Lohuizen57333362019-04-01 14:35:09 +0200169 fmt.Fprintf(g.w, "}},\n")
170 g.processCUE(dir)
171 fmt.Fprintf(g.w, "},\n")
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100172}
173
174func (g *generator) sep() {
175 if g.first {
176 g.first = false
177 return
178 }
179 fmt.Fprintln(g.w, "}, {")
180}
181
182func importName(s *ast.ImportSpec) string {
183 if s.Name != nil {
184 return s.Name.Name
185 }
186 pkg, err := strconv.Unquote(s.Path.Value)
187 if err != nil {
188 log.Fatal(err)
189 }
190 return path.Base(pkg)
191}
192
Marcel van Lohuizen57333362019-04-01 14:35:09 +0200193// processCUE mixes in CUE definitions defined in the package directory.
194func (g *generator) processCUE(dir string) {
195 instances := cue.Build(load.Instances([]string{dir}, &load.Config{
196 StdRoot: "../pkg",
197 }))
198
199 if err := instances[0].Err; err != nil {
200 if !strings.Contains(err.Error(), "no CUE files") {
201 log.Fatal(err)
202 }
203 return
204 }
205
Marcel van Lohuizenf8132852019-04-26 12:16:18 +0200206 n := instances[0].Value().Syntax(cue.Hidden(true), cue.Concrete(false))
Marcel van Lohuizen57333362019-04-01 14:35:09 +0200207 var buf bytes.Buffer
Marcel van Lohuizenf8132852019-04-26 12:16:18 +0200208 if err := cueformat.Node(&buf, n); err != nil {
Marcel van Lohuizen57333362019-04-01 14:35:09 +0200209 log.Fatal(err)
210 }
211 body := buf.String()
212 body = strings.ReplaceAll(body, "\n\n", "\n")
213 // body = strings.ReplaceAll(body, "\t", "")
214 // TODO: escape backtick
215 fmt.Fprintf(g.w, "cue: `%s`,\n", body)
216}
217
218func (g *generator) processGo(filename string) {
219 if strings.HasSuffix(filename, "_test.go") {
220 return
221 }
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100222 f, err := parser.ParseFile(g.fset, filename, nil, parser.ParseComments)
223 if err != nil {
224 log.Fatal(err)
225 }
226 g.defaultPkg = ""
227
228 for _, d := range f.Decls {
229 switch x := d.(type) {
230 case *ast.GenDecl:
231 switch x.Tok {
232 case token.CONST:
233 for _, spec := range x.Specs {
Marcel van Lohuizen928bfa32019-04-26 12:16:18 +0200234 if !ast.IsExported(spec.(*ast.ValueSpec).Names[0].Name) {
235 continue
236 }
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100237 g.genConst(spec.(*ast.ValueSpec))
238 }
239 case token.IMPORT:
240 for _, s := range x.Specs {
241 spec := s.(*ast.ImportSpec)
242 g.imports = append(g.imports, spec)
243 }
244 if g.defaultPkg == "" {
245 g.defaultPkg = importName(x.Specs[0].(*ast.ImportSpec))
246 }
247 case token.VAR:
248 for _, spec := range x.Specs {
249 if ast.IsExported(spec.(*ast.ValueSpec).Names[0].Name) {
250 log.Fatal("var declarations not supported")
251 }
252 }
253 printer.Fprint(g.decls, g.fset, x)
254 fmt.Fprint(g.decls, "\n\n")
255 continue
256 case token.TYPE:
Marcel van Lohuizenf8132852019-04-26 12:16:18 +0200257 // TODO: support type declarations.
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100258 for _, spec := range x.Specs {
259 if ast.IsExported(spec.(*ast.TypeSpec).Name.Name) {
260 log.Fatal("type declarations not supported")
261 }
262 }
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100263 continue
264 default:
265 log.Fatalf("unexpected spec of type %s", x.Tok)
266 }
267 case *ast.FuncDecl:
268 g.genFun(x)
269 }
270 }
271}
272
273func (g *generator) genConst(spec *ast.ValueSpec) {
274 name := spec.Names[0].Name
275 value := ""
276 switch v := g.toValue(spec.Values[0]); v.Kind() {
Marcel van Lohuizen8bc02e52019-04-01 13:14:07 +0200277 case constant.Bool, constant.Int, constant.String:
278 // TODO: convert octal numbers
279 value = v.ExactString()
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100280 case constant.Float:
281 var rat big.Rat
282 rat.SetString(v.ExactString())
283 var float big.Float
284 float.SetRat(&rat)
Marcel van Lohuizen8bc02e52019-04-01 13:14:07 +0200285 value = float.Text('g', -1)
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100286 default:
287 fmt.Printf("Dropped entry %s.%s (%T: %v)\n", g.defaultPkg, name, v.Kind(), v.ExactString())
288 return
289 }
290 g.sep()
Marcel van Lohuizen8bc02e52019-04-01 13:14:07 +0200291 fmt.Fprintf(g.w, "Name: %q,\n Const: %q,\n", name, value)
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100292}
293
294func (g *generator) toValue(x ast.Expr) constant.Value {
295 switch x := x.(type) {
296 case *ast.BasicLit:
297 return constant.MakeFromLiteral(x.Value, x.Kind, 0)
298 case *ast.BinaryExpr:
299 return constant.BinaryOp(g.toValue(x.X), x.Op, g.toValue(x.Y))
300 case *ast.UnaryExpr:
301 return constant.UnaryOp(x.Op, g.toValue(x.X), 0)
302 default:
303 log.Fatalf("%s: unsupported expression type %T: %#v", g.defaultPkg, x, x)
304 }
305 return constant.MakeUnknown()
306}
307
308func (g *generator) genFun(x *ast.FuncDecl) {
309 if x.Body == nil {
310 return
311 }
312 types := []string{}
313 if x.Type.Results != nil {
314 for _, f := range x.Type.Results.List {
315 if len(f.Names) > 0 {
316 for range f.Names {
317 types = append(types, g.goKind(f.Type))
318 }
319 } else {
320 types = append(types, g.goKind(f.Type))
321 }
322 }
323 }
324 if n := len(types); n != 1 && (n != 2 || types[1] != "error") {
325 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)
Marcel van Lohuizen10fd4962019-04-26 12:14:49 +0200326 return
327 }
328
329 if !ast.IsExported(x.Name.Name) || x.Recv != nil {
330 return
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100331 }
332
333 g.sep()
334 fmt.Fprintf(g.w, "Name: %q,\n", x.Name.Name)
335
336 args := []string{}
337 vals := []string{}
338 kind := []string{}
339 omitCheck := true
340 for _, f := range x.Type.Params.List {
341 for _, name := range f.Names {
342 typ := g.goKind(f.Type)
343 argKind, ground := g.goToCUE(f.Type)
344 if !ground {
345 omitCheck = false
346 }
347 vals = append(vals, fmt.Sprintf("c.%s(%d)", typ, len(args)))
348 args = append(args, name.Name)
349 kind = append(kind, argKind)
350 }
351 }
352
353 fmt.Fprintf(g.w, "Params: []kind{%s},\n", strings.Join(kind, ", "))
354 result, _ := g.goToCUE(x.Type.Results.List[0].Type)
355 fmt.Fprintf(g.w, "Result: %s,\n", result)
356 argList := strings.Join(args, ", ")
357 valList := strings.Join(vals, ", ")
358 init := ""
359 if len(args) > 0 {
360 init = fmt.Sprintf("%s := %s", argList, valList)
361 }
362
363 fmt.Fprintf(g.w, "Func: func(c *callCtxt) {")
364 defer fmt.Fprintln(g.w, "},")
365 fmt.Fprintln(g.w)
366 if init != "" {
367 fmt.Fprintln(g.w, init)
368 }
369 if !omitCheck {
370 fmt.Fprintln(g.w, "if c.do() {")
371 defer fmt.Fprintln(g.w, "}")
372 }
373 if len(types) == 1 {
374 fmt.Fprint(g.w, "c.ret = func() interface{} ")
375 } else {
376 fmt.Fprint(g.w, "c.ret, c.err = func() (interface{}, error) ")
377 }
378 printer.Fprint(g.w, g.fset, x.Body)
379 fmt.Fprintln(g.w, "()")
380}
381
382func (g *generator) goKind(expr ast.Expr) string {
383 if star, isStar := expr.(*ast.StarExpr); isStar {
384 expr = star.X
385 }
386 w := &bytes.Buffer{}
387 printer.Fprint(w, g.fset, expr)
388 switch str := w.String(); str {
389 case "big.Int":
390 return "bigInt"
391 case "big.Float":
392 return "bigFloat"
393 case "big.Rat":
394 return "bigRat"
395 case "cue.Value":
396 return "value"
397 case "cue.List":
398 return "list"
399 case "[]string":
400 return "strList"
401 case "[]byte":
402 return "bytes"
403 case "io.Reader":
404 return "reader"
Marcel van Lohuizen57333362019-04-01 14:35:09 +0200405 case "time.Time":
406 return "string"
Marcel van Lohuizen7b5d2fd2018-12-11 16:34:56 +0100407 default:
408 return str
409 }
410}
411
412func (g *generator) goToCUE(expr ast.Expr) (cueKind string, omitCheck bool) {
413 // TODO: detect list and structs types for return values.
414 omitCheck = true
415 switch k := g.goKind(expr); k {
416 case "error":
417 cueKind += "bottomKind"
418 case "bool":
419 cueKind += "boolKind"
420 case "string", "bytes", "reader":
421 cueKind += "stringKind"
422 case "int", "int8", "int16", "int32", "rune", "int64",
423 "uint", "byte", "uint8", "uint16", "uint32", "uint64",
424 "bigInt":
425 cueKind += "intKind"
426 case "float64", "bigRat", "bigFloat":
427 cueKind += "numKind"
428 case "list":
429 cueKind += "listKind"
430 case "strList":
431 omitCheck = false
432 cueKind += "listKind"
433 case "value":
434 // Must use callCtxt.value method for these types and resolve manually.
435 cueKind += "topKind" // TODO: can be more precise
436 default:
437 switch {
438 case strings.HasPrefix(k, "[]"):
439 cueKind += "listKind"
440 case strings.HasPrefix(k, "map["):
441 cueKind += "structKind"
442 default:
443 // log.Println("Unknown type:", k)
444 // Must use callCtxt.value method for these types and resolve manually.
445 cueKind += "topKind" // TODO: can be more precise
446 omitCheck = false
447 }
448 }
449 return cueKind, omitCheck
450}