cuelang.org/go@v0.10.1/internal/core/runtime/extern.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 runtime 16 17 import ( 18 "cuelang.org/go/cue/ast" 19 "cuelang.org/go/cue/build" 20 "cuelang.org/go/cue/errors" 21 "cuelang.org/go/cue/format" 22 "cuelang.org/go/cue/token" 23 "cuelang.org/go/internal" 24 "cuelang.org/go/internal/core/adt" 25 "cuelang.org/go/internal/core/walk" 26 ) 27 28 // SetInterpreter sets the interpreter for interpretation of files marked with 29 // @extern(kind). 30 func (r *Runtime) SetInterpreter(i Interpreter) { 31 if r.interpreters == nil { 32 r.interpreters = map[string]Interpreter{} 33 } 34 r.interpreters[i.Kind()] = i 35 } 36 37 // TODO: consider also passing the top-level attribute to NewCompiler to allow 38 // passing default values. 39 40 // Interpreter defines an entrypoint for creating per-package interpreters. 41 type Interpreter interface { 42 // NewCompiler creates a compiler for b and reports any errors. 43 NewCompiler(b *build.Instance, r *Runtime) (Compiler, errors.Error) 44 45 // Kind returns the string to be used in the file-level @extern attribute. 46 Kind() string 47 } 48 49 // A Compiler fills in an adt.Expr for fields marked with `@extern(kind)`. 50 type Compiler interface { 51 // Compile creates an adt.Expr (usually a builtin) for the 52 // given external named resource (usually a function). name 53 // is the name of the resource to compile, taken from altName 54 // in `@extern(name=altName)`, or from the field name if that's 55 // not defined. Scope is the struct that contains the field. 56 // Other than "name", the fields in a are implementation 57 // specific. 58 Compile(name string, scope adt.Value, a *internal.Attr) (adt.Expr, errors.Error) 59 } 60 61 // injectImplementations modifies v to include implementations of functions 62 // for fields associated with the @extern attributes. 63 func (r *Runtime) injectImplementations(b *build.Instance, v *adt.Vertex) (errs errors.Error) { 64 if r.interpreters == nil { 65 return nil 66 } 67 68 d := &externDecorator{ 69 runtime: r, 70 pkg: b, 71 } 72 73 for _, f := range b.Files { 74 d.errs = errors.Append(d.errs, d.addFile(f)) 75 } 76 77 v.VisitLeafConjuncts(func(c adt.Conjunct) bool { 78 d.decorateConjunct(c.Elem(), v) 79 return true 80 }) 81 82 return d.errs 83 } 84 85 // externDecorator locates extern attributes and calls the relevant interpreters 86 // to inject builtins. 87 // 88 // This is a two-pass algorithm: in the first pass, all ast.Files are processed 89 // to build an index from *ast.Fields to attributes. In the second phase, the 90 // corresponding adt.Fields are located in the ADT and decorated with the 91 // builtins. 92 type externDecorator struct { 93 runtime *Runtime 94 pkg *build.Instance 95 96 compilers map[string]Compiler 97 fields map[*ast.Field]fieldInfo 98 99 errs errors.Error 100 } 101 102 type fieldInfo struct { 103 extern string 104 funcName string 105 attrBody string 106 attr *ast.Attribute 107 } 108 109 // addFile finds injection points in the given ast.File for external 110 // implementations of Builtins. 111 func (d *externDecorator) addFile(f *ast.File) (errs errors.Error) { 112 kind, pos, decls, err := findExternFileAttr(f) 113 if len(decls) == 0 { 114 return err 115 } 116 117 ok, err := d.initCompiler(kind, pos) 118 if !ok { 119 return err 120 } 121 122 return d.markExternFieldAttr(kind, decls) 123 } 124 125 // findExternFileAttr reports the extern kind of a file-level @extern(kind) 126 // attribute in f, the position of the corresponding attribute, and f's 127 // declarations from the package directive onwards. It's an error if more than 128 // one @extern attribute is found. decls == nil signals that this file should be 129 // skipped. 130 func findExternFileAttr(f *ast.File) (kind string, pos token.Pos, decls []ast.Decl, err errors.Error) { 131 var ( 132 hasPkg bool 133 p int 134 fileAttr *ast.Attribute 135 ) 136 137 loop: 138 for ; p < len(f.Decls); p++ { 139 switch a := f.Decls[p].(type) { 140 case *ast.Package: 141 hasPkg = true 142 break loop 143 144 case *ast.Attribute: 145 pos = a.Pos() 146 key, body := a.Split() 147 if key != "extern" { 148 continue 149 } 150 fileAttr = a 151 152 attr := internal.ParseAttrBody(a.Pos(), body) 153 if attr.Err != nil { 154 return "", pos, nil, attr.Err 155 } 156 k, err := attr.String(0) 157 if err != nil { 158 // Unreachable. 159 return "", pos, nil, errors.Newf(a.Pos(), "%s", err) 160 } 161 162 if k == "" { 163 return "", pos, nil, errors.Newf(a.Pos(), 164 "interpreter name must be non-empty") 165 } 166 167 if kind != "" { 168 return "", pos, nil, errors.Newf(a.Pos(), 169 "only one file-level extern attribute allowed per file") 170 171 } 172 kind = k 173 } 174 } 175 176 switch { 177 case fileAttr == nil && !hasPkg: 178 // Nothing to see here. 179 return "", pos, nil, nil 180 181 case fileAttr != nil && !hasPkg: 182 return "", pos, nil, errors.Newf(fileAttr.Pos(), 183 "extern attribute without package clause") 184 185 case fileAttr == nil && hasPkg: 186 // Check that there are no top-level extern attributes. 187 for p++; p < len(f.Decls); p++ { 188 x, ok := f.Decls[p].(*ast.Attribute) 189 if !ok { 190 continue 191 } 192 if key, _ := x.Split(); key == "extern" { 193 err = errors.Append(err, errors.Newf(x.Pos(), 194 "extern attribute must appear before package clause")) 195 } 196 } 197 return "", pos, nil, err 198 } 199 200 return kind, pos, f.Decls[p:], nil 201 } 202 203 // initCompiler initializes the runtime for kind, if applicable. The pos 204 // argument represents the position of the file-level @extern attribute. 205 func (d *externDecorator) initCompiler(kind string, pos token.Pos) (ok bool, err errors.Error) { 206 if c, ok := d.compilers[kind]; ok { 207 return c != nil, nil 208 } 209 210 // initialize the compiler. 211 if d.compilers == nil { 212 d.compilers = map[string]Compiler{} 213 d.fields = map[*ast.Field]fieldInfo{} 214 } 215 216 x := d.runtime.interpreters[kind] 217 if x == nil { 218 return false, errors.Newf(pos, "no interpreter defined for %q", kind) 219 } 220 221 c, err := x.NewCompiler(d.pkg, d.runtime) 222 if err != nil { 223 return false, err 224 } 225 226 d.compilers[kind] = c 227 228 return c != nil, nil 229 } 230 231 // markExternFieldAttr collects all *ast.Fields with extern attributes into 232 // d.fields. Both of the following forms are allowed: 233 // 234 // a: _ @extern(...) 235 // a: { _, @extern(...) } 236 // 237 // consistent with attribute implementation recommendations. 238 func (d *externDecorator) markExternFieldAttr(kind string, decls []ast.Decl) (errs errors.Error) { 239 var fieldStack []*ast.Field 240 241 ast.Walk(&ast.File{Decls: decls}, func(n ast.Node) bool { 242 switch x := n.(type) { 243 case *ast.Field: 244 fieldStack = append(fieldStack, x) 245 246 case *ast.Attribute: 247 key, body := x.Split() 248 // Support old-style and new-style extern attributes. 249 if key != "extern" && key != kind { 250 break 251 } 252 253 lastField := len(fieldStack) - 1 254 if lastField < 0 { 255 errs = errors.Append(errs, errors.Newf(x.Pos(), 256 "@%s attribute not associated with field", kind)) 257 return true 258 } 259 260 f := fieldStack[lastField] 261 262 if _, ok := d.fields[f]; ok { 263 errs = errors.Append(errs, errors.Newf(x.Pos(), 264 "duplicate @%s attributes", kind)) 265 return true 266 } 267 268 name, isIdent, err := ast.LabelName(f.Label) 269 if err != nil || !isIdent { 270 b, _ := format.Node(f.Label) 271 errs = errors.Append(errs, errors.Newf(x.Pos(), 272 "can only define functions for fields with identifier names, found %v", string(b))) 273 return true 274 } 275 276 d.fields[f] = fieldInfo{ 277 extern: kind, 278 funcName: name, 279 attrBody: body, 280 attr: x, 281 } 282 } 283 284 return true 285 286 }, func(n ast.Node) { 287 switch n.(type) { 288 case *ast.Field: 289 fieldStack = fieldStack[:len(fieldStack)-1] 290 } 291 }) 292 293 return errs 294 } 295 296 func (d *externDecorator) decorateConjunct(e adt.Elem, scope *adt.Vertex) { 297 w := walk.Visitor{Before: func(n adt.Node) bool { 298 return d.processADTNode(n, scope) 299 }} 300 w.Elem(e) 301 } 302 303 // processADTNode injects a builtin conjunct into n if n is an adt.Field and 304 // has a marked ast.Field associated with it. 305 func (d *externDecorator) processADTNode(n adt.Node, scope *adt.Vertex) bool { 306 f, ok := n.(*adt.Field) 307 if !ok { 308 return true 309 } 310 311 info, ok := d.fields[f.Src] 312 if !ok { 313 return true 314 } 315 316 c, ok := d.compilers[info.extern] 317 if !ok { 318 // An error for a missing runtime was already reported earlier, 319 // if applicable. 320 return true 321 } 322 323 attr := internal.ParseAttrBody(info.attr.Pos(), info.attrBody) 324 if attr.Err != nil { 325 d.errs = errors.Append(d.errs, attr.Err) 326 return true 327 } 328 name := info.funcName 329 if str, ok, _ := attr.Lookup(1, "name"); ok { 330 name = str 331 } 332 333 b, err := c.Compile(name, scope, &attr) 334 if err != nil { 335 err = errors.Wrap(errors.Newf(info.attr.Pos(), "@%s", info.extern), err) 336 d.errs = errors.Append(d.errs, err) 337 return true 338 } 339 340 f.Value = &adt.BinaryExpr{ 341 Op: adt.AndOp, 342 X: f.Value, 343 Y: b, 344 } 345 346 return true 347 }