blob: 2eb37d96fd6dd7d7818d4cdf438d4c2ec2eefbfe [file] [log] [blame]
// 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
}