...

Source file src/cuelang.org/go/internal/core/runtime/extern.go

Documentation: cuelang.org/go/internal/core/runtime

     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  	for _, c := range v.Conjuncts {
    78  		d.decorateConjunct(c.Elem(), v)
    79  	}
    80  
    81  	return d.errs
    82  }
    83  
    84  // externDecorator locates extern attributes and calls the relevant interpreters
    85  // to inject builtins.
    86  //
    87  // This is a two-pass algorithm: in the first pass, all ast.Files are processed
    88  // to build an index from *ast.Fields to attributes. In the second phase, the
    89  // corresponding adt.Fields are located in the ADT and decorated with the
    90  // builtins.
    91  type externDecorator struct {
    92  	runtime *Runtime
    93  	pkg     *build.Instance
    94  
    95  	compilers map[string]Compiler
    96  	fields    map[*ast.Field]fieldInfo
    97  
    98  	errs errors.Error
    99  }
   100  
   101  type fieldInfo struct {
   102  	file     *ast.File
   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  			if key != "extern" {
   249  				break
   250  			}
   251  
   252  			lastField := len(fieldStack) - 1
   253  			if lastField < 0 {
   254  				errs = errors.Append(errs, errors.Newf(x.Pos(),
   255  					"extern attribute not associated with field"))
   256  				return true
   257  			}
   258  
   259  			f := fieldStack[lastField]
   260  
   261  			if _, ok := d.fields[f]; ok {
   262  				errs = errors.Append(errs, errors.Newf(x.Pos(),
   263  					"duplicate extern attributes"))
   264  				return true
   265  			}
   266  
   267  			name, isIdent, err := ast.LabelName(f.Label)
   268  			if err != nil || !isIdent {
   269  				b, _ := format.Node(f.Label)
   270  				errs = errors.Append(errs, errors.Newf(x.Pos(),
   271  					"can only define functions for fields with identifier names, found %v", string(b)))
   272  				return true
   273  			}
   274  
   275  			d.fields[f] = fieldInfo{
   276  				extern:   kind,
   277  				funcName: name,
   278  				attrBody: body,
   279  				attr:     x,
   280  			}
   281  		}
   282  
   283  		return true
   284  
   285  	}, func(n ast.Node) {
   286  		switch n.(type) {
   287  		case *ast.Field:
   288  			fieldStack = fieldStack[:len(fieldStack)-1]
   289  		}
   290  	})
   291  
   292  	return errs
   293  }
   294  
   295  func (d *externDecorator) decorateConjunct(e adt.Elem, scope *adt.Vertex) {
   296  	w := walk.Visitor{Before: func(n adt.Node) bool {
   297  		return d.processADTNode(n, scope)
   298  	}}
   299  	w.Elem(e)
   300  }
   301  
   302  // processADTNode injects a builtin conjunct into n if n is an adt.Field and
   303  // has a marked ast.Field associated with it.
   304  func (d *externDecorator) processADTNode(n adt.Node, scope *adt.Vertex) bool {
   305  	f, ok := n.(*adt.Field)
   306  	if !ok {
   307  		return true
   308  	}
   309  
   310  	info, ok := d.fields[f.Src]
   311  	if !ok {
   312  		return true
   313  	}
   314  
   315  	c, ok := d.compilers[info.extern]
   316  	if !ok {
   317  		// An error for a missing runtime was already reported earlier,
   318  		// if applicable.
   319  		return true
   320  	}
   321  
   322  	attr := internal.ParseAttrBody(info.attr.Pos(), info.attrBody)
   323  	if attr.Err != nil {
   324  		d.errs = errors.Append(d.errs, attr.Err)
   325  		return true
   326  	}
   327  	name := info.funcName
   328  	if str, ok, _ := attr.Lookup(1, "name"); ok {
   329  		name = str
   330  	}
   331  
   332  	b, err := c.Compile(name, scope, &attr)
   333  	if err != nil {
   334  		err = errors.Newf(info.attr.Pos(), "can't load from external module: %v", err)
   335  		d.errs = errors.Append(d.errs, err)
   336  		return true
   337  	}
   338  
   339  	f.Value = &adt.BinaryExpr{
   340  		Op: adt.AndOp,
   341  		X:  f.Value,
   342  		Y:  b,
   343  	}
   344  
   345  	return true
   346  }
   347  

View as plain text