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 }