| // 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. |
| |
| //go:generate go run golang.org/x/tools/cmd/goimports -w -local cuelang.org/go builtins.go |
| //go:generate gofmt -s -w builtins.go |
| |
| package cue |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "io" |
| "math/big" |
| "path" |
| "sort" |
| "strings" |
| |
| "github.com/cockroachdb/apd/v2" |
| |
| "cuelang.org/go/cue/errors" |
| "cuelang.org/go/cue/parser" |
| "cuelang.org/go/cue/token" |
| "cuelang.org/go/internal" |
| "cuelang.org/go/internal/core/adt" |
| "cuelang.org/go/internal/core/compile" |
| "cuelang.org/go/internal/core/convert" |
| ) |
| |
| // A Builtin is a builtin function or constant. |
| // |
| // A function may return and a constant may be any of the following types: |
| // |
| // error (translates to bottom) |
| // nil (translates to null) |
| // bool |
| // int* |
| // uint* |
| // float64 |
| // string |
| // *big.Float |
| // *big.Int |
| // |
| // For any of the above, including interface{} and these types recursively: |
| // []T |
| // map[string]T |
| // |
| type Builtin struct { |
| Name string |
| Pkg adt.Feature |
| Params []adt.Kind |
| Result adt.Kind |
| Func func(c *CallCtxt) |
| Const string |
| } |
| |
| type Package struct { |
| Native []*Builtin |
| CUE string |
| } |
| |
| func (p *Package) MustCompile(ctx *adt.OpContext, pkgName string) *adt.Vertex { |
| obj := &adt.Vertex{} |
| pkgLabel := ctx.StringLabel(pkgName) |
| st := &adt.StructLit{} |
| if len(p.Native) > 0 { |
| obj.AddConjunct(adt.MakeConjunct(nil, st)) |
| } |
| for _, b := range p.Native { |
| b.Pkg = pkgLabel |
| |
| f := ctx.StringLabel(b.Name) // never starts with _ |
| // n := &node{baseValue: newBase(imp.Path)} |
| var v adt.Expr = toBuiltin(ctx, b) |
| if b.Const != "" { |
| v = mustParseConstBuiltin(ctx, b.Name, b.Const) |
| } |
| st.Decls = append(st.Decls, &adt.Field{ |
| Label: f, |
| Value: v, |
| }) |
| } |
| |
| // Parse builtin CUE |
| if p.CUE != "" { |
| expr, err := parser.ParseExpr(pkgName, p.CUE) |
| if err != nil { |
| panic(fmt.Errorf("could not parse %v: %v", p.CUE, err)) |
| } |
| c, err := compile.Expr(nil, ctx.Runtime, expr) |
| if err != nil { |
| panic(fmt.Errorf("could compile parse %v: %v", p.CUE, err)) |
| } |
| obj.AddConjunct(c) |
| } |
| |
| // We could compile lazily, but this is easier for debugging. |
| obj.Finalize(ctx) |
| if err := obj.Err(ctx, adt.Finalized); err != nil { |
| panic(err.Err) |
| } |
| |
| return obj |
| } |
| |
| func toBuiltin(ctx *adt.OpContext, b *Builtin) *adt.Builtin { |
| x := &adt.Builtin{ |
| Params: b.Params, |
| Result: b.Result, |
| Package: b.Pkg, |
| Name: b.Name, |
| } |
| x.Func = func(ctx *adt.OpContext, args []adt.Value) (ret adt.Expr) { |
| // call, _ := ctx.Source().(*ast.CallExpr) |
| c := &CallCtxt{ |
| // src: call, |
| ctx: ctx, |
| args: args, |
| builtin: b, |
| } |
| defer func() { |
| var errVal interface{} = c.Err |
| if err := recover(); err != nil { |
| errVal = err |
| } |
| ret = processErr(c, errVal, ret) |
| }() |
| b.Func(c) |
| switch v := c.Ret.(type) { |
| case adt.Value: |
| return v |
| case bottomer: |
| return v.Bottom() |
| } |
| if c.Err != nil { |
| return nil |
| } |
| return convert.GoValueToValue(ctx, c.Ret, true) |
| } |
| return x |
| } |
| |
| // newConstBuiltin parses and creates any CUE expression that does not have |
| // fields. |
| func mustParseConstBuiltin(ctx *adt.OpContext, name, val string) adt.Expr { |
| expr, err := parser.ParseExpr("<builtin:"+name+">", val) |
| if err != nil { |
| panic(err) |
| } |
| c, err := compile.Expr(nil, ctx, expr) |
| if err != nil { |
| panic(err) |
| } |
| return c.Expr() |
| |
| } |
| |
| func pos(n adt.Node) (p token.Pos) { |
| if n == nil { |
| return |
| } |
| src := n.Source() |
| if src == nil { |
| return |
| } |
| return src.Pos() |
| } |
| |
| func (x *Builtin) name(ctx *adt.OpContext) string { |
| if x.Pkg == 0 { |
| return x.Name |
| } |
| return fmt.Sprintf("%s.%s", x.Pkg.StringValue(ctx), x.Name) |
| } |
| |
| func (x *Builtin) isValidator() bool { |
| return len(x.Params) == 1 && x.Result == adt.BoolKind |
| } |
| |
| func processErr(call *CallCtxt, errVal interface{}, ret adt.Expr) adt.Expr { |
| ctx := call.ctx |
| switch err := errVal.(type) { |
| case nil: |
| case *callError: |
| ret = err.b |
| case *json.MarshalerError: |
| if err, ok := err.Err.(bottomer); ok { |
| if b := err.Bottom(); b != nil { |
| ret = b |
| } |
| } |
| case bottomer: |
| ret = wrapCallErr(call, err.Bottom()) |
| case errors.Error: |
| ret = wrapCallErr(call, &adt.Bottom{Err: err}) |
| case error: |
| if call.Err == internal.ErrIncomplete { |
| err := ctx.NewErrf("incomplete value") |
| err.Code = adt.IncompleteError |
| ret = err |
| } else { |
| // TODO: store the underlying error explicitly |
| ret = wrapCallErr(call, &adt.Bottom{Err: errors.Promote(err, "")}) |
| } |
| default: |
| // Likely a string passed to panic. |
| ret = wrapCallErr(call, &adt.Bottom{ |
| Err: errors.Newf(call.Pos(), "%s", err), |
| }) |
| } |
| return ret |
| } |
| |
| func wrapCallErr(c *CallCtxt, b *adt.Bottom) *adt.Bottom { |
| pos := token.NoPos |
| if c.src != nil { |
| if src := c.src.Source(); src != nil { |
| pos = src.Pos() |
| } |
| } |
| const msg = "error in call to %s" |
| return &adt.Bottom{ |
| Code: b.Code, |
| Err: errors.Wrapf(b.Err, pos, msg, c.builtin.name(c.ctx)), |
| } |
| } |
| |
| func (c *CallCtxt) convertError(x interface{}, name string) *adt.Bottom { |
| var err errors.Error |
| switch v := x.(type) { |
| case nil: |
| return nil |
| |
| case *adt.Bottom: |
| return v |
| |
| case *json.MarshalerError: |
| err = errors.Promote(v, "marshal error") |
| |
| case errors.Error: |
| err = v |
| |
| case error: |
| if name != "" { |
| err = errors.Newf(c.Pos(), "%s: %v", name, v) |
| } else { |
| err = errors.Newf(c.Pos(), "error in call to %s: %v", c.Name(), v) |
| } |
| |
| default: |
| err = errors.Newf(token.NoPos, "%s", name) |
| } |
| if err != internal.ErrIncomplete { |
| return &adt.Bottom{ |
| // Wrap to preserve position information. |
| Err: errors.Wrapf(err, c.Pos(), "error in call to %s", c.Name()), |
| } |
| } |
| return &adt.Bottom{ |
| Code: adt.IncompleteError, |
| Err: errors.Newf(c.Pos(), "incomplete values in call to %s", c.Name()), |
| } |
| } |
| |
| // CallCtxt is passed to builtin implementations that need to use a cue.Value. This is an internal type. It's interface may change. |
| type CallCtxt struct { |
| src adt.Expr // *adt.CallExpr |
| ctx *adt.OpContext |
| builtin *Builtin |
| Err interface{} |
| Ret interface{} |
| |
| args []adt.Value |
| } |
| |
| func (c *CallCtxt) Pos() token.Pos { |
| return c.ctx.Pos() |
| } |
| |
| func (c *CallCtxt) Name() string { |
| return c.builtin.name(c.ctx) |
| } |
| |
| var builtins = map[string]*Instance{} |
| |
| func initBuiltins(pkgs map[string]*Package) { |
| ctx := sharedIndex.newContext().opCtx |
| keys := []string{} |
| for k := range pkgs { |
| keys = append(keys, k) |
| } |
| sort.Strings(keys) |
| for _, k := range keys { |
| b := pkgs[k] |
| e := b.MustCompile(ctx, k) |
| |
| i := sharedIndex.addInst(&Instance{ |
| ImportPath: k, |
| PkgName: path.Base(k), |
| root: e, |
| }) |
| |
| builtins[k] = i |
| builtins["-/"+path.Base(k)] = i |
| } |
| } |
| |
| func getBuiltinPkg(ctx *context, path string) *structLit { |
| p, ok := builtins[path] |
| if !ok { |
| return nil |
| } |
| return p.root |
| } |
| |
| func init() { |
| internal.UnifyBuiltin = func(val interface{}, kind string) interface{} { |
| v := val.(Value) |
| ctx := v.ctx() |
| |
| p := strings.Split(kind, ".") |
| pkg, name := p[0], p[1] |
| s := getBuiltinPkg(ctx, pkg) |
| if s == nil { |
| return v |
| } |
| a := s.Lookup(ctx.Label(name, false)) |
| if a == nil { |
| return v |
| } |
| |
| return v.Unify(makeValue(v.idx, a)) |
| } |
| } |
| |
| // Do returns whether the call should be done. |
| func (c *CallCtxt) Do() bool { |
| return c.Err == nil |
| } |
| |
| type bottomer interface { |
| error |
| Bottom() *adt.Bottom |
| } |
| |
| type callError struct { |
| b *adt.Bottom |
| } |
| |
| func (e *callError) Error() string { |
| return fmt.Sprint(e.b) |
| } |
| |
| func (c *CallCtxt) errf(src adt.Node, underlying error, format string, args ...interface{}) { |
| var errs errors.Error |
| if err, ok := underlying.(bottomer); ok { |
| errs = err.Bottom().Err |
| } |
| errs = errors.Wrapf(errs, c.ctx.Pos(), format, args...) |
| c.Err = &callError{&adt.Bottom{Err: errs}} |
| } |
| |
| func (c *CallCtxt) errcf(src adt.Node, code adt.ErrorCode, format string, args ...interface{}) { |
| err := c.ctx.NewErrf(format, args...) |
| err.Code = code |
| c.Err = &callError{err} |
| } |
| |
| func (c *CallCtxt) Value(i int) Value { |
| v := MakeValue(c.ctx, c.args[i]) |
| // TODO: remove default |
| // v, _ = v.Default() |
| if !v.IsConcrete() { |
| c.errcf(c.src, adt.IncompleteError, "non-concrete argument %d", i) |
| } |
| return v |
| } |
| |
| func (c *CallCtxt) Struct(i int) *Struct { |
| v := MakeValue(c.ctx, c.args[i]) |
| s, err := v.Struct() |
| if err != nil { |
| c.invalidArgType(c.args[i], i, "struct", err) |
| return nil |
| } |
| return s |
| } |
| |
| func (c *CallCtxt) invalidArgType(arg adt.Expr, i int, typ string, err error) { |
| if ve, ok := err.(bottomer); ok && ve.Bottom().IsIncomplete() { |
| c.Err = ve |
| return |
| } |
| v, ok := arg.(adt.Value) |
| // TODO: make these permanent errors if the value did not originate from |
| // a reference. |
| if !ok { |
| c.errf(c.src, nil, |
| "cannot use incomplete value %s as %s in argument %d to %s: %v", |
| c.ctx.Str(arg), typ, i, c.Name(), err) |
| } |
| if err != nil { |
| c.errf(c.src, err, |
| "cannot use %s (type %s) as %s in argument %d to %s: %v", |
| c.ctx.Str(arg), v.Kind(), typ, i, c.Name(), err) |
| } else { |
| c.errf(c.src, err, |
| "cannot use %s (type %s) as %s in argument %d to %s", |
| c.ctx.Str(arg), v.Kind(), typ, i, c.Name()) |
| } |
| } |
| |
| func (c *CallCtxt) Int(i int) int { return int(c.intValue(i, 64, "int64")) } |
| func (c *CallCtxt) Int8(i int) int8 { return int8(c.intValue(i, 8, "int8")) } |
| func (c *CallCtxt) Int16(i int) int16 { return int16(c.intValue(i, 16, "int16")) } |
| func (c *CallCtxt) Int32(i int) int32 { return int32(c.intValue(i, 32, "int32")) } |
| func (c *CallCtxt) Rune(i int) rune { return rune(c.intValue(i, 32, "rune")) } |
| func (c *CallCtxt) Int64(i int) int64 { return int64(c.intValue(i, 64, "int64")) } |
| |
| func (c *CallCtxt) intValue(i, bits int, typ string) int64 { |
| arg := c.args[i] |
| x := MakeValue(c.ctx, arg) |
| n, err := x.Int(nil) |
| if err != nil { |
| c.invalidArgType(arg, i, typ, err) |
| return 0 |
| } |
| if n.BitLen() > bits { |
| c.errf(c.src, err, "int %s overflows %s in argument %d in call to %s", |
| n, typ, i, c.Name()) |
| } |
| res, _ := x.Int64() |
| return res |
| } |
| |
| func (c *CallCtxt) Uint(i int) uint { return uint(c.uintValue(i, 64, "uint64")) } |
| func (c *CallCtxt) Uint8(i int) uint8 { return uint8(c.uintValue(i, 8, "uint8")) } |
| func (c *CallCtxt) Byte(i int) uint8 { return byte(c.uintValue(i, 8, "byte")) } |
| func (c *CallCtxt) Uint16(i int) uint16 { return uint16(c.uintValue(i, 16, "uint16")) } |
| func (c *CallCtxt) Uint32(i int) uint32 { return uint32(c.uintValue(i, 32, "uint32")) } |
| func (c *CallCtxt) Uint64(i int) uint64 { return uint64(c.uintValue(i, 64, "uint64")) } |
| |
| func (c *CallCtxt) uintValue(i, bits int, typ string) uint64 { |
| x := MakeValue(c.ctx, c.args[i]) |
| n, err := x.Int(nil) |
| if err != nil || n.Sign() < 0 { |
| c.invalidArgType(c.args[i], i, typ, err) |
| return 0 |
| } |
| if n.BitLen() > bits { |
| c.errf(c.src, err, "int %s overflows %s in argument %d in call to %s", |
| n, typ, i, c.Name()) |
| } |
| res, _ := x.Uint64() |
| return res |
| } |
| |
| func (c *CallCtxt) Decimal(i int) *apd.Decimal { |
| x := MakeValue(c.ctx, c.args[i]) |
| if _, err := x.MantExp(nil); err != nil { |
| c.invalidArgType(c.args[i], i, "Decimal", err) |
| return nil |
| } |
| return &c.args[i].(*adt.Num).X |
| } |
| |
| func (c *CallCtxt) Float64(i int) float64 { |
| x := MakeValue(c.ctx, c.args[i]) |
| res, err := x.Float64() |
| if err != nil { |
| c.invalidArgType(c.args[i], i, "float64", err) |
| return 0 |
| } |
| return res |
| } |
| |
| func (c *CallCtxt) BigInt(i int) *big.Int { |
| x := MakeValue(c.ctx, c.args[i]) |
| n, err := x.Int(nil) |
| if err != nil { |
| c.invalidArgType(c.args[i], i, "int", err) |
| return nil |
| } |
| return n |
| } |
| |
| var ten = big.NewInt(10) |
| |
| func (c *CallCtxt) BigFloat(i int) *big.Float { |
| x := MakeValue(c.ctx, c.args[i]) |
| var mant big.Int |
| exp, err := x.MantExp(&mant) |
| if err != nil { |
| c.invalidArgType(c.args[i], i, "float", err) |
| return nil |
| } |
| f := &big.Float{} |
| f.SetInt(&mant) |
| if exp != 0 { |
| var g big.Float |
| e := big.NewInt(int64(exp)) |
| f.Mul(f, g.SetInt(e.Exp(ten, e, nil))) |
| } |
| return f |
| } |
| |
| func (c *CallCtxt) String(i int) string { |
| x := MakeValue(c.ctx, c.args[i]) |
| v, err := x.String() |
| if err != nil { |
| c.invalidArgType(c.args[i], i, "string", err) |
| return "" |
| } |
| return v |
| } |
| |
| func (c *CallCtxt) Bytes(i int) []byte { |
| x := MakeValue(c.ctx, c.args[i]) |
| v, err := x.Bytes() |
| if err != nil { |
| c.invalidArgType(c.args[i], i, "bytes", err) |
| return nil |
| } |
| return v |
| } |
| |
| func (c *CallCtxt) Reader(i int) io.Reader { |
| x := MakeValue(c.ctx, c.args[i]) |
| // TODO: optimize for string and bytes cases |
| r, err := x.Reader() |
| if err != nil { |
| c.invalidArgType(c.args[i], i, "bytes|string", err) |
| return nil |
| } |
| return r |
| } |
| |
| func (c *CallCtxt) Bool(i int) bool { |
| x := MakeValue(c.ctx, c.args[i]) |
| b, err := x.Bool() |
| if err != nil { |
| c.invalidArgType(c.args[i], i, "bool", err) |
| return false |
| } |
| return b |
| } |
| |
| func (c *CallCtxt) List(i int) (a []Value) { |
| arg := c.args[i] |
| x := MakeValue(c.ctx, arg) |
| v, err := x.List() |
| if err != nil { |
| c.invalidArgType(c.args[i], i, "list", err) |
| return a |
| } |
| for v.Next() { |
| a = append(a, v.Value()) |
| } |
| return a |
| } |
| |
| func (c *CallCtxt) Iter(i int) (a Iterator) { |
| arg := c.args[i] |
| x := MakeValue(c.ctx, arg) |
| v, err := x.List() |
| if err != nil { |
| c.invalidArgType(c.args[i], i, "list", err) |
| } |
| return v |
| } |
| |
| func (c *CallCtxt) DecimalList(i int) (a []*apd.Decimal) { |
| arg := c.args[i] |
| x := MakeValue(c.ctx, arg) |
| v, err := x.List() |
| if err != nil { |
| c.invalidArgType(c.args[i], i, "list", err) |
| return nil |
| } |
| for j := 0; v.Next(); j++ { |
| num, err := v.Value().Decimal() |
| if err != nil { |
| c.errf(c.src, err, "invalid list element %d in argument %d to %s: %v", |
| j, i, c.Name(), err) |
| break |
| } |
| a = append(a, num) |
| } |
| return a |
| } |
| |
| func (c *CallCtxt) StringList(i int) (a []string) { |
| arg := c.args[i] |
| x := MakeValue(c.ctx, arg) |
| v, err := x.List() |
| if err != nil { |
| c.invalidArgType(c.args[i], i, "list", err) |
| return nil |
| } |
| for j := 0; v.Next(); j++ { |
| str, err := v.Value().String() |
| if err != nil { |
| c.Err = errors.Wrapf(err, c.Pos(), |
| "element %d of list argument %d", j, i) |
| break |
| } |
| a = append(a, str) |
| } |
| return a |
| } |