cuelang.org/go@v0.13.0/cue/interpreter/wasm/builtin.go (about) 1 // Copyright 2023 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 wasm 16 17 import ( 18 "cuelang.org/go/cue" 19 "cuelang.org/go/internal/core/adt" 20 "cuelang.org/go/internal/pkg" 21 "cuelang.org/go/internal/value" 22 ) 23 24 // generateCallThatReturnsBuiltin returns a call expression to a nullary 25 // builtin that returns another builtin that corresponds to the external 26 // Wasm function declared by the user. name is the name of the function, 27 // args are its declared arguments, scope is a CUE value that represents 28 // the structure into which to resolve the arguments and i is the 29 // loaded Wasm instance that contains the function. 30 // 31 // This function is implemented as a higher-order function to solve a 32 // bootstrapping issue. The user can specifies arbitrary types for the 33 // function's arguments, and these types can be arbitrary CUE types 34 // defined in arbitrary places. This function is called before CUE 35 // evaluation at a time where identifiers are not yet resolved. This 36 // higher-order design solves the bootstrapping issue by deferring the 37 // resolution of identifiers (and selectors, and expressions in general) 38 // until runtime. At compile-time we only generate a nullary builtin 39 // that we call, and being nullary, it does not need to refer to any 40 // unresolved identifiers, rather the identifiers (and selectors) are 41 // saved in the closure that executes later, at runtime. 42 // 43 // Additionally, resolving identifiers (and selectors) requires an 44 // OpContext, and we do not have an OpContext at compile time. With 45 // this higher-order design we get an appropiate OpContext when the 46 // runtime calls the nullary builtin hence solving the bootstrapping 47 // problem. 48 func generateCallThatReturnsBuiltin(name string, scope adt.Value, args []string, i *instance) (adt.Expr, error) { 49 // ensure that the function exists before trying to call it. 50 _, err := i.load(name) 51 if err != nil { 52 return nil, err 53 } 54 55 call := &adt.CallExpr{Fun: &adt.Builtin{ 56 Result: adt.TopKind, 57 Name: name, 58 Func: func(call *adt.CallContext) adt.Expr { 59 opctx := call.OpContext() 60 61 scope := value.Make(opctx, scope) 62 sig := compileStringsInScope(args, scope) 63 args, result := splitLast(sig) 64 b := &pkg.Builtin{ 65 Name: name, 66 Params: params(args), 67 Result: result.Kind(), 68 Func: cABIFunc(i, name, sig), 69 } 70 return pkg.ToBuiltin(b) 71 }, 72 }} 73 return call, nil 74 } 75 76 // param converts a CUE value that represents the type of a function 77 // argument into its pkg.Param counterpart. 78 func param(arg cue.Value) pkg.Param { 79 param := pkg.Param{ 80 Kind: arg.IncompleteKind(), 81 } 82 if param.Kind == adt.StructKind || ((param.Kind & adt.NumberKind) != 0) { 83 _, v := value.ToInternal(arg) 84 param.Value = v 85 } 86 return param 87 } 88 89 // params converts a list of CUE values that represent the types of a 90 // function's arguments into their pkg.Param counterparts. 91 func params(args []cue.Value) []pkg.Param { 92 var params []pkg.Param 93 for _, a := range args { 94 params = append(params, param(a)) 95 } 96 return params 97 }