cuelang.org/go@v0.13.0/internal/pkg/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 pkg 16 17 import ( 18 "encoding/json" 19 "fmt" 20 21 "cuelang.org/go/cue/errors" 22 "cuelang.org/go/cue/parser" 23 "cuelang.org/go/internal" 24 "cuelang.org/go/internal/core/adt" 25 "cuelang.org/go/internal/core/compile" 26 "cuelang.org/go/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 type Builtin struct { 47 Name string 48 Pkg adt.Feature 49 Params []Param 50 Result adt.Kind 51 NonConcrete bool 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 79 if b.Const != "" { 80 v = mustParseConstBuiltin(ctx, b.Name, b.Const) 81 } else { 82 v = ToBuiltin(b) 83 } 84 st.Decls = append(st.Decls, &adt.Field{ 85 Label: f, 86 Value: v, 87 }) 88 } 89 90 // Parse builtin CUE 91 if p.CUE != "" { 92 expr, err := parser.ParseExpr(importPath, p.CUE) 93 if err != nil { 94 panic(fmt.Errorf("could not parse %v: %v", p.CUE, err)) 95 } 96 c, err := compile.Expr(nil, ctx.Runtime, importPath, expr) 97 if err != nil { 98 panic(fmt.Errorf("could compile parse %v: %v", p.CUE, err)) 99 } 100 obj.AddConjunct(c) 101 } 102 103 // We could compile lazily, but this is easier for debugging. 104 obj.Finalize(ctx) 105 if err := obj.Err(ctx); err != nil { 106 panic(err.Err) 107 } 108 109 return obj 110 } 111 112 // ToBuiltin converts a Builtin into an adt.Builtin. 113 func ToBuiltin(b *Builtin) *adt.Builtin { 114 params := make([]adt.Param, len(b.Params)) 115 for i, p := range b.Params { 116 params[i].Value = p.Value 117 if params[i].Value == nil { 118 params[i].Value = &adt.BasicType{K: p.Kind} 119 } 120 } 121 122 x := &adt.Builtin{ 123 Params: params, 124 Result: b.Result, 125 NonConcrete: b.NonConcrete, 126 Package: b.Pkg, 127 Name: b.Name, 128 } 129 x.Func = func(call *adt.CallContext) (ret adt.Expr) { 130 ctx := call.OpContext() 131 args := call.Args() 132 133 // call, _ := ctx.Source().(*ast.CallExpr) 134 c := &CallCtxt{ 135 CallContext: call, 136 ctx: ctx, 137 args: args, 138 builtin: b, 139 } 140 defer func() { 141 var errVal interface{} = c.Err 142 if err := recover(); err != nil { 143 errVal = err 144 } 145 ret = processErr(c, errVal, ret) 146 }() 147 b.Func(c) 148 switch v := c.Ret.(type) { 149 case nil: 150 // Validators may return a nil in case validation passes. 151 return nil 152 case *adt.Bottom: 153 // deal with API limitation: catch nil interface issue. 154 if v != nil { 155 return v 156 } 157 return nil 158 case adt.Value: 159 return v 160 case Bottomer: 161 // deal with API limitation: catch nil interface issue. 162 if b := v.Bottom(); b != nil { 163 return b 164 } 165 return nil 166 } 167 if c.Err != nil { 168 if _, ok := c.Err.(ValidationError); !ok || c.ctx.IsValidator { 169 return nil 170 } 171 } 172 return convert.GoValueToValue(ctx, c.Ret, true) 173 } 174 return x 175 } 176 177 // newConstBuiltin parses and creates any CUE expression that does not have 178 // fields. 179 func mustParseConstBuiltin(ctx adt.Runtime, name, val string) adt.Expr { 180 expr, err := parser.ParseExpr("<builtin:"+name+">", val) 181 if err != nil { 182 panic(err) 183 } 184 c, err := compile.Expr(nil, ctx, "_", expr) 185 if err != nil { 186 panic(err) 187 } 188 return c.Expr() 189 190 } 191 192 func (x *Builtin) name(ctx *adt.OpContext) string { 193 if x.Pkg == 0 { 194 return x.Name 195 } 196 return fmt.Sprintf("%s.%s", x.Pkg.StringValue(ctx), x.Name) 197 } 198 199 func processErr(call *CallCtxt, errVal interface{}, ret adt.Expr) adt.Expr { 200 ctx := call.ctx 201 switch err := errVal.(type) { 202 case nil: 203 case ValidationError: 204 if call.ctx.IsValidator { 205 ret = err.B 206 } 207 case *adt.Bottom: 208 ret = err 209 case *callError: 210 ret = err.b 211 case *json.MarshalerError: 212 if err, ok := err.Err.(Bottomer); ok { 213 if b := err.Bottom(); b != nil { 214 ret = b 215 } 216 } 217 case Bottomer: 218 ret = wrapCallErr(call, err.Bottom()) 219 220 case errors.Error: 221 // Convert lists of errors to a combined Bottom error. 222 if list := errors.Errors(err); len(list) != 0 && list[0] != errVal { 223 var errs *adt.Bottom 224 for _, err := range list { 225 if b, ok := processErr(call, err, nil).(*adt.Bottom); ok { 226 errs = adt.CombineErrors(nil, errs, b) 227 } 228 } 229 if errs != nil { 230 return errs 231 } 232 } 233 234 ret = wrapCallErr(call, &adt.Bottom{Err: err}) 235 case error: 236 if call.Err == internal.ErrIncomplete { 237 err := ctx.NewErrf("incomplete value") 238 err.Code = adt.IncompleteError 239 ret = err 240 } else { 241 // TODO: store the underlying error explicitly 242 ret = wrapCallErr(call, &adt.Bottom{Err: errors.Promote(err, "")}) 243 } 244 default: 245 // Likely a string passed to panic. 246 ret = wrapCallErr(call, &adt.Bottom{ 247 Err: errors.Newf(call.Pos(), "%s", err), 248 }) 249 } 250 return ret 251 }