| // Copyright 2020 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. |
| |
| package internal |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| |
| "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 []Param |
| Result adt.Kind |
| Func func(c *CallCtxt) |
| Const string |
| } |
| |
| type Param struct { |
| Kind adt.Kind |
| Value adt.Value // input constraint (may be nil) |
| } |
| |
| type Package struct { |
| Native []*Builtin |
| CUE string |
| } |
| |
| func (p *Package) MustCompile(ctx *adt.OpContext, importPath string) *adt.Vertex { |
| obj := &adt.Vertex{} |
| pkgLabel := ctx.StringLabel(importPath) |
| st := &adt.StructLit{} |
| if len(p.Native) > 0 { |
| obj.AddConjunct(adt.MakeRootConjunct(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(importPath, p.CUE) |
| if err != nil { |
| panic(fmt.Errorf("could not parse %v: %v", p.CUE, err)) |
| } |
| c, err := compile.Expr(nil, ctx.Runtime, importPath, 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 { |
| params := make([]adt.Param, len(b.Params)) |
| for i, p := range b.Params { |
| params[i].Value = p.Value |
| if params[i].Value == nil { |
| params[i].Value = &adt.BasicType{K: p.Kind} |
| } |
| } |
| |
| x := &adt.Builtin{ |
| Params: 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 nil: |
| // Validators may return a nil in case validation passes. |
| return nil |
| case *adt.Bottom: |
| // deal with API limitation: catch nil interface issue. |
| if v != nil { |
| return v |
| } |
| return nil |
| case adt.Value: |
| return v |
| case bottomer: |
| // deal with API limitation: catch nil interface issue. |
| if b := v.Bottom(); b != nil { |
| return b |
| } |
| return nil |
| } |
| 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.Runtime, 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 *adt.Bottom: |
| ret = err |
| 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 |
| } |