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

     1  // Copyright 2020 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 internal
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  
    21  	"github.com/joomcode/cue/cue/errors"
    22  	"github.com/joomcode/cue/cue/parser"
    23  	"github.com/joomcode/cue/internal"
    24  	"github.com/joomcode/cue/internal/core/adt"
    25  	"github.com/joomcode/cue/internal/core/compile"
    26  	"github.com/joomcode/cue/internal/core/convert"
    27  )
    28  
    29  // A Builtin is a Builtin function or constant.
    30  //
    31  // A function may return and a constant may be any of the following types:
    32  //
    33  //   error (translates to bottom)
    34  //   nil   (translates to null)
    35  //   bool
    36  //   int*
    37  //   uint*
    38  //   float64
    39  //   string
    40  //   *big.Float
    41  //   *big.Int
    42  //
    43  //   For any of the above, including interface{} and these types recursively:
    44  //   []T
    45  //   map[string]T
    46  //
    47  type Builtin struct {
    48  	Name   string
    49  	Pkg    adt.Feature
    50  	Params []Param
    51  	Result adt.Kind
    52  	Func   func(c *CallCtxt)
    53  	Const  string
    54  }
    55  
    56  type Param struct {
    57  	Kind  adt.Kind
    58  	Value adt.Value // input constraint (may be nil)
    59  }
    60  
    61  type Package struct {
    62  	Native []*Builtin
    63  	CUE    string
    64  }
    65  
    66  func (p *Package) MustCompile(ctx *adt.OpContext, importPath string) *adt.Vertex {
    67  	obj := &adt.Vertex{}
    68  	pkgLabel := ctx.StringLabel(importPath)
    69  	st := &adt.StructLit{}
    70  	if len(p.Native) > 0 {
    71  		obj.AddConjunct(adt.MakeRootConjunct(nil, st))
    72  	}
    73  	for _, b := range p.Native {
    74  		b.Pkg = pkgLabel
    75  
    76  		f := ctx.StringLabel(b.Name) // never starts with _
    77  		// n := &node{baseValue: newBase(imp.Path)}
    78  		var v adt.Expr = toBuiltin(ctx, b)
    79  		if b.Const != "" {
    80  			v = mustParseConstBuiltin(ctx, b.Name, b.Const)
    81  		}
    82  		st.Decls = append(st.Decls, &adt.Field{
    83  			Label: f,
    84  			Value: v,
    85  		})
    86  	}
    87  
    88  	// Parse builtin CUE
    89  	if p.CUE != "" {
    90  		expr, err := parser.ParseExpr(importPath, p.CUE)
    91  		if err != nil {
    92  			panic(fmt.Errorf("could not parse %v: %v", p.CUE, err))
    93  		}
    94  		c, err := compile.Expr(nil, ctx.Runtime, importPath, expr)
    95  		if err != nil {
    96  			panic(fmt.Errorf("could compile parse %v: %v", p.CUE, err))
    97  		}
    98  		obj.AddConjunct(c)
    99  	}
   100  
   101  	// We could compile lazily, but this is easier for debugging.
   102  	obj.Finalize(ctx)
   103  	if err := obj.Err(ctx, adt.Finalized); err != nil {
   104  		panic(err.Err)
   105  	}
   106  
   107  	return obj
   108  }
   109  
   110  func toBuiltin(ctx *adt.OpContext, b *Builtin) *adt.Builtin {
   111  	params := make([]adt.Param, len(b.Params))
   112  	for i, p := range b.Params {
   113  		params[i].Value = p.Value
   114  		if params[i].Value == nil {
   115  			params[i].Value = &adt.BasicType{K: p.Kind}
   116  		}
   117  	}
   118  
   119  	x := &adt.Builtin{
   120  		Params:  params,
   121  		Result:  b.Result,
   122  		Package: b.Pkg,
   123  		Name:    b.Name,
   124  	}
   125  	x.Func = func(ctx *adt.OpContext, args []adt.Value) (ret adt.Expr) {
   126  		// call, _ := ctx.Source().(*ast.CallExpr)
   127  		c := &CallCtxt{
   128  			ctx:     ctx,
   129  			args:    args,
   130  			builtin: b,
   131  		}
   132  		defer func() {
   133  			var errVal interface{} = c.Err
   134  			if err := recover(); err != nil {
   135  				errVal = err
   136  			}
   137  			ret = processErr(c, errVal, ret)
   138  		}()
   139  		b.Func(c)
   140  		switch v := c.Ret.(type) {
   141  		case nil:
   142  			// Validators may return a nil in case validation passes.
   143  			return nil
   144  		case *adt.Bottom:
   145  			// deal with API limitation: catch nil interface issue.
   146  			if v != nil {
   147  				return v
   148  			}
   149  			return nil
   150  		case adt.Value:
   151  			return v
   152  		case bottomer:
   153  			// deal with API limitation: catch nil interface issue.
   154  			if b := v.Bottom(); b != nil {
   155  				return b
   156  			}
   157  			return nil
   158  		}
   159  		if c.Err != nil {
   160  			return nil
   161  		}
   162  		return convert.GoValueToValue(ctx, c.Ret, true)
   163  	}
   164  	return x
   165  }
   166  
   167  // newConstBuiltin parses and creates any CUE expression that does not have
   168  // fields.
   169  func mustParseConstBuiltin(ctx adt.Runtime, name, val string) adt.Expr {
   170  	expr, err := parser.ParseExpr("<builtin:"+name+">", val)
   171  	if err != nil {
   172  		panic(err)
   173  	}
   174  	c, err := compile.Expr(nil, ctx, "_", expr)
   175  	if err != nil {
   176  		panic(err)
   177  	}
   178  	return c.Expr()
   179  
   180  }
   181  
   182  func (x *Builtin) name(ctx *adt.OpContext) string {
   183  	if x.Pkg == 0 {
   184  		return x.Name
   185  	}
   186  	return fmt.Sprintf("%s.%s", x.Pkg.StringValue(ctx), x.Name)
   187  }
   188  
   189  func (x *Builtin) isValidator() bool {
   190  	return len(x.Params) == 1 && x.Result == adt.BoolKind
   191  }
   192  
   193  func processErr(call *CallCtxt, errVal interface{}, ret adt.Expr) adt.Expr {
   194  	ctx := call.ctx
   195  	switch err := errVal.(type) {
   196  	case nil:
   197  	case *adt.Bottom:
   198  		ret = err
   199  	case *callError:
   200  		ret = err.b
   201  	case *json.MarshalerError:
   202  		if err, ok := err.Err.(bottomer); ok {
   203  			if b := err.Bottom(); b != nil {
   204  				ret = b
   205  			}
   206  		}
   207  	case bottomer:
   208  		ret = wrapCallErr(call, err.Bottom())
   209  
   210  	case errors.Error:
   211  		// Convert lists of errors to a combined Bottom error.
   212  		if list := errors.Errors(err); len(list) != 0 && list[0] != errVal {
   213  			var errs *adt.Bottom
   214  			for _, err := range list {
   215  				if b, ok := processErr(call, err, nil).(*adt.Bottom); ok {
   216  					errs = adt.CombineErrors(nil, errs, b)
   217  				}
   218  			}
   219  			if errs != nil {
   220  				return errs
   221  			}
   222  		}
   223  
   224  		ret = wrapCallErr(call, &adt.Bottom{Err: err})
   225  	case error:
   226  		if call.Err == internal.ErrIncomplete {
   227  			err := ctx.NewErrf("incomplete value")
   228  			err.Code = adt.IncompleteError
   229  			ret = err
   230  		} else {
   231  			// TODO: store the underlying error explicitly
   232  			ret = wrapCallErr(call, &adt.Bottom{Err: errors.Promote(err, "")})
   233  		}
   234  	default:
   235  		// Likely a string passed to panic.
   236  		ret = wrapCallErr(call, &adt.Bottom{
   237  			Err: errors.Newf(call.Pos(), "%s", err),
   238  		})
   239  	}
   240  	return ret
   241  }