From 7617de2b03a76b3fb1e2d635443853993f6b80b5 Mon Sep 17 00:00:00 2001 From: Marcel van Lohuizen Date: Tue, 28 Feb 2023 17:36:18 +0100 Subject: [PATCH] cue/context: support internal interpreters This introduces the hooks needed for implementing a WASM interpreter to be used in CUE. This API, for now, only supports internal types. Signed-off-by: Marcel van Lohuizen Change-Id: Id201419028ca8cbac5883809cdbd53ab7aa440cf Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/550265 Reviewed-by: Roger Peppe Unity-Result: CUEcueckoo TryBot-Result: CUEcueckoo --- cue/cuecontext/cuecontext.go | 19 +- internal/attrs.go | 2 +- internal/core/runtime/build.go | 2 + internal/core/runtime/extern.go | 340 ++++++++++++++++++ internal/core/runtime/extern_test.go | 96 +++++ internal/core/runtime/runtime.go | 4 + internal/core/runtime/testdata/_debug.txtar | 13 + internal/core/runtime/testdata/basic.txtar | 30 ++ internal/core/runtime/testdata/compile.txtar | 8 + internal/core/runtime/testdata/errors.txtar | 113 ++++++ internal/core/runtime/testdata/failinit.txtar | 12 + internal/core/runtime/testdata/nested.txtar | 34 ++ .../core/runtime/testdata/no_top_extern.txtar | 21 ++ internal/core/runtime/testdata/none.txtar | 7 + internal/core/runtime/testdata/nopkg.txtar | 10 + internal/core/runtime/testdata/nullinit.txtar | 14 + internal/core/walk/walk.go | 8 +- 17 files changed, 730 insertions(+), 3 deletions(-) create mode 100644 internal/core/runtime/extern.go create mode 100644 internal/core/runtime/extern_test.go create mode 100644 internal/core/runtime/testdata/_debug.txtar create mode 100644 internal/core/runtime/testdata/basic.txtar create mode 100644 internal/core/runtime/testdata/compile.txtar create mode 100644 internal/core/runtime/testdata/errors.txtar create mode 100644 internal/core/runtime/testdata/failinit.txtar create mode 100644 internal/core/runtime/testdata/nested.txtar create mode 100644 internal/core/runtime/testdata/no_top_extern.txtar create mode 100644 internal/core/runtime/testdata/none.txtar create mode 100644 internal/core/runtime/testdata/nopkg.txtar create mode 100644 internal/core/runtime/testdata/nullinit.txtar diff --git a/cue/cuecontext/cuecontext.go b/cue/cuecontext/cuecontext.go index 06080793e31..1d25463a7db 100644 --- a/cue/cuecontext/cuecontext.go +++ b/cue/cuecontext/cuecontext.go @@ -22,10 +22,27 @@ import ( ) // Option controls a build context. -type Option interface{ buildOption() } +type Option struct { + apply func(r *runtime.Runtime) +} // New creates a new Context. func New(options ...Option) *cue.Context { r := runtime.New() + for _, o := range options { + o.apply(r) + } return (*cue.Context)(r) } + +// An ExternInterpreter creates a compiler that can produce implementations of +// functions written in a language other than CUE. It is currently for internal +// use only. +type ExternInterpreter = runtime.Interpreter + +// Interpreter associates an interpreter for external code with this context. +func Interpreter(i ExternInterpreter) Option { + return Option{func(r *runtime.Runtime) { + r.SetInterpreter(i) + }} +} diff --git a/internal/attrs.go b/internal/attrs.go index 058948012f0..268cec33e8f 100644 --- a/internal/attrs.go +++ b/internal/attrs.go @@ -51,7 +51,7 @@ type Attr struct { Body string Kind AttrKind Fields []KeyValue - Err error + Err errors.Error } // NewNonExisting creates a non-existing attribute. diff --git a/internal/core/runtime/build.go b/internal/core/runtime/build.go index 1fec9ec7388..7d35a4321ff 100644 --- a/internal/core/runtime/build.go +++ b/internal/core/runtime/build.go @@ -80,6 +80,8 @@ func (x *Runtime) Build(cfg *Config, b *build.Instance) (v *adt.Vertex, errs err v, err = compile.Files(cc, x, b.ID(), b.Files...) errs = errors.Append(errs, err) + errs = errors.Append(errs, x.injectImplementations(b, v)) + if errs != nil { v = adt.ToVertex(&adt.Bottom{Err: errs}) b.Err = errs diff --git a/internal/core/runtime/extern.go b/internal/core/runtime/extern.go new file mode 100644 index 00000000000..e07919ede3f --- /dev/null +++ b/internal/core/runtime/extern.go @@ -0,0 +1,340 @@ +// Copyright 2023 CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package runtime + +import ( + "cuelang.org/go/cue/ast" + "cuelang.org/go/cue/build" + "cuelang.org/go/cue/errors" + "cuelang.org/go/cue/format" + "cuelang.org/go/cue/token" + "cuelang.org/go/internal" + "cuelang.org/go/internal/core/adt" + "cuelang.org/go/internal/core/walk" +) + +// SetInterpreter sets the interpreter for interpretation of files marked with +// @extern(kind). +func (r *Runtime) SetInterpreter(i Interpreter) { + if r.interpreters == nil { + r.interpreters = map[string]Interpreter{} + } + r.interpreters[i.Kind()] = i +} + +// TODO: consider also passing the top-level attribute to NewCompiler to allow +// passing default values. + +// Interpreter defines an entrypoint for creating per-package interpreters. +type Interpreter interface { + // NewCompiler creates a compiler for b and reports any errors. + NewCompiler(b *build.Instance) (Compiler, errors.Error) + + // Kind returns the string to be used in the file-level @extern attribute. + Kind() string +} + +// A Compiler composes an adt.Builtin for an external function implementation. +type Compiler interface { + // Compile creates a builtin for the given function name and attribute. + // funcName is the name of the function to compile, taken from altName in + // @extern(name=altName), or from the field name if that's not defined. + // Other than "name", the fields in a are implementation specific. + Compile(funcName string, a *internal.Attr) (*adt.Builtin, errors.Error) +} + +// injectImplementations modifies v to include implementations of functions +// for fields associated with the @extern attributes. +func (r *Runtime) injectImplementations(b *build.Instance, v *adt.Vertex) (errs errors.Error) { + if r.interpreters == nil { + return nil + } + + d := &externDecorator{ + runtime: r, + pkg: b, + } + + for _, f := range b.Files { + d.errs = errors.Append(d.errs, d.addFile(f)) + } + + for _, c := range v.Conjuncts { + d.decorateConjunct(c.Elem()) + } + + return d.errs +} + +// externDecorator locates extern attributes and calls the relevant interpreters +// to inject builtins. +// +// This is a two-pass algorithm: in the first pass, all ast.Files are processed +// to build an index from *ast.Fields to attributes. In the second phase, the +// corresponding adt.Fields are located in the ADT and decorated with the +// builtins. +type externDecorator struct { + runtime *Runtime + pkg *build.Instance + + compilers map[string]Compiler + fields map[*ast.Field]fieldInfo + + errs errors.Error +} + +type fieldInfo struct { + file *ast.File + extern string + funcName string + attrBody string + attr *ast.Attribute +} + +// addFile finds injection points in the given ast.File for external +// implementations of Builtins. +func (d *externDecorator) addFile(f *ast.File) (errs errors.Error) { + kind, pos, decls, err := findExternFileAttr(f) + if len(decls) == 0 { + return err + } + + ok, err := d.initCompiler(kind, pos) + if !ok { + return err + } + + return d.markExternFieldAttr(kind, decls) +} + +// findExternFileAttr reports the extern kind of a file-level @extern(kind) +// attribute in f, the position of the corresponding attribute, and f's +// declarations from the package directive onwards. It's an error if more than +// one @extern attribute is found. decls == nil signals that this file should be +// skipped. +func findExternFileAttr(f *ast.File) (kind string, pos token.Pos, decls []ast.Decl, err errors.Error) { + var ( + hasPkg bool + p int + fileAttr *ast.Attribute + ) + +loop: + for ; p < len(f.Decls); p++ { + switch a := f.Decls[p].(type) { + case *ast.Package: + hasPkg = true + break loop + + case *ast.Attribute: + pos = a.Pos() + key, body := a.Split() + if key != "extern" { + continue + } + fileAttr = a + + attr := internal.ParseAttrBody(a.Pos(), body) + if attr.Err != nil { + return "", pos, nil, attr.Err + } + k, err := attr.String(0) + if err != nil { + // Unreachable. + return "", pos, nil, errors.Newf(a.Pos(), "%s", err) + } + + if k == "" { + return "", pos, nil, errors.Newf(a.Pos(), + "interpreter name must be non-empty") + } + + if kind != "" { + return "", pos, nil, errors.Newf(a.Pos(), + "only one file-level extern attribute allowed per file") + + } + kind = k + } + } + + switch { + case fileAttr == nil && !hasPkg: + // Nothing to see here. + return "", pos, nil, nil + + case fileAttr != nil && !hasPkg: + return "", pos, nil, errors.Newf(fileAttr.Pos(), + "extern attribute without package clause") + + case fileAttr == nil && hasPkg: + // Check that there are no top-level extern attributes. + for p++; p < len(f.Decls); p++ { + x, ok := f.Decls[p].(*ast.Attribute) + if !ok { + continue + } + if key, _ := x.Split(); key == "extern" { + err = errors.Append(err, errors.Newf(x.Pos(), + "extern attribute must appear before package clause")) + } + } + return "", pos, nil, err + } + + return kind, pos, f.Decls[p:], nil +} + +// initCompiler initializes the runtime for kind, if applicable. The pos +// argument represents the position of the file-level @extern attribute. +func (d *externDecorator) initCompiler(kind string, pos token.Pos) (ok bool, err errors.Error) { + if c, ok := d.compilers[kind]; ok { + return c != nil, nil + } + + // initialize the compiler. + if d.compilers == nil { + d.compilers = map[string]Compiler{} + d.fields = map[*ast.Field]fieldInfo{} + } + + x := d.runtime.interpreters[kind] + if x == nil { + return false, errors.Newf(pos, "no interpreter defined for %q", kind) + } + + c, err := x.NewCompiler(d.pkg) + if err != nil { + return false, err + } + + d.compilers[kind] = c + + return c != nil, nil +} + +// markExternFieldAttr collects all *ast.Fields with extern attributes into +// d.fields. Both of the following forms are allowed: +// +// a: _ @extern(...) +// a: { _, @extern(...) } +// +// consistent with attribute implementation recommendations. +func (d *externDecorator) markExternFieldAttr(kind string, decls []ast.Decl) (errs errors.Error) { + var fieldStack []*ast.Field + + ast.Walk(&ast.File{Decls: decls}, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.Field: + fieldStack = append(fieldStack, x) + + case *ast.Attribute: + key, body := x.Split() + if key != "extern" { + break + } + + lastField := len(fieldStack) - 1 + if lastField < 0 { + errs = errors.Append(errs, errors.Newf(x.Pos(), + "extern attribute not associated with field")) + return true + } + + f := fieldStack[lastField] + + if _, ok := d.fields[f]; ok { + errs = errors.Append(errs, errors.Newf(x.Pos(), + "duplicate extern attributes")) + return true + } + + name, isIdent, err := ast.LabelName(f.Label) + if err != nil || !isIdent { + b, _ := format.Node(f.Label) + errs = errors.Append(errs, errors.Newf(x.Pos(), + "can only define functions for fields with identifier names, found %v", string(b))) + return true + } + + d.fields[f] = fieldInfo{ + extern: kind, + funcName: name, + attrBody: body, + attr: x, + } + } + + return true + + }, func(n ast.Node) { + switch n.(type) { + case *ast.Field: + fieldStack = fieldStack[:len(fieldStack)-1] + } + }) + + return errs +} + +func (d *externDecorator) decorateConjunct(e adt.Elem) { + w := walk.Visitor{Before: d.processADTNode} + w.Elem(e) +} + +// processADTNode injects a builtin conjunct into n if n is an adt.Field and +// has a marked ast.Field associated with it. +func (d *externDecorator) processADTNode(n adt.Node) bool { + f, ok := n.(*adt.Field) + if !ok { + return true + } + + info, ok := d.fields[f.Src] + if !ok { + return true + } + + c, ok := d.compilers[info.extern] + if !ok { + // An error for a missing runtime was already reported earlier, + // if applicable. + return true + } + + attr := internal.ParseAttrBody(info.attr.Pos(), info.attrBody) + if attr.Err != nil { + d.errs = errors.Append(d.errs, attr.Err) + return true + } + name := info.funcName + if str, ok, _ := attr.Lookup(1, "name"); ok { + name = str + } + + b, err := c.Compile(name, &attr) + if err != nil { + d.errs = errors.Append(d.errs, err) + return true + } + + f.Value = &adt.BinaryExpr{ + Op: adt.AndOp, + X: f.Value, + Y: b, + } + + return true +} diff --git a/internal/core/runtime/extern_test.go b/internal/core/runtime/extern_test.go new file mode 100644 index 00000000000..9f469d46bf5 --- /dev/null +++ b/internal/core/runtime/extern_test.go @@ -0,0 +1,96 @@ +// Copyright 2023 CUE Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package runtime_test + +import ( + "fmt" + "strconv" + "testing" + + "cuelang.org/go/cue/build" + "cuelang.org/go/cue/cuecontext" + "cuelang.org/go/cue/errors" + "cuelang.org/go/cue/token" + "cuelang.org/go/internal" + "cuelang.org/go/internal/core/adt" + "cuelang.org/go/internal/core/runtime" + "cuelang.org/go/internal/cuetxtar" +) + +func Test(t *testing.T) { + test := cuetxtar.TxTarTest{ + Root: "testdata/", + Name: "extern", + } + + test.Run(t, func(t *cuetxtar.Test) { + interpreter := &interpreterFake{files: map[string]int{}} + ctx := cuecontext.New(cuecontext.Interpreter(interpreter)) + + b := t.Instance() + v := ctx.BuildInstance(b) + if err := v.Err(); err != nil { + t.WriteErrors(errors.Promote(err, "test")) + return + } + + fmt.Fprintf(t, "%v\n", v) + }) +} + +type interpreterFake struct { + files map[string]int +} + +func (i *interpreterFake) Kind() string { return "test" } + +func (i *interpreterFake) NewCompiler(b *build.Instance) (runtime.Compiler, errors.Error) { + switch b.PkgName { + case "failinit": + return nil, errors.Newf(token.NoPos, "TEST: fail initialization") + case "nullinit": + return nil, nil + } + return i, nil +} + +func (i *interpreterFake) Compile(funcName string, a *internal.Attr) (*adt.Builtin, errors.Error) { + if ok, _ := a.Flag(1, "fail"); ok { + return nil, errors.Newf(token.NoPos, "TEST: fail compilation") + } + + str, ok, err := a.Lookup(1, "err") + if err != nil { + return nil, errors.Promote(err, "test") + } + + if ok { + return nil, errors.Newf(token.NoPos, "%s", str) + } + + if str, err = a.String(0); err != nil { + return nil, errors.Promote(err, "test") + } + + if _, ok := i.files[str]; !ok { + i.files[str] = len(i.files) + 1 + } + + return &adt.Builtin{ + Name: "impl" + funcName + strconv.Itoa(i.files[str]), + Params: []adt.Param{{Value: &adt.BasicType{K: adt.IntKind}}}, + Result: adt.IntKind, + }, nil +} diff --git a/internal/core/runtime/runtime.go b/internal/core/runtime/runtime.go index f4569807734..4cf7bdd1146 100644 --- a/internal/core/runtime/runtime.go +++ b/internal/core/runtime/runtime.go @@ -23,6 +23,10 @@ type Runtime struct { index *index loaded map[*build.Instance]interface{} + + // interpreters implement extern functionality. The map key corresponds to + // the kind in a file-level @extern(kind) attribute. + interpreters map[string]Interpreter } func (r *Runtime) SetBuildData(b *build.Instance, x interface{}) { diff --git a/internal/core/runtime/testdata/_debug.txtar b/internal/core/runtime/testdata/_debug.txtar new file mode 100644 index 00000000000..c7d2282f100 --- /dev/null +++ b/internal/core/runtime/testdata/_debug.txtar @@ -0,0 +1,13 @@ +// This file is ordered first and is added as a convenience debugging tool. +// Do not remove. +-- a_debug.cue -- +@extern("test") + +package foo + +Foo: _ @extern(file1.xx, abi=c, sig="func(int)int") + +-- out/extern -- +{ + Foo: implFoo1 +} diff --git a/internal/core/runtime/testdata/basic.txtar b/internal/core/runtime/testdata/basic.txtar new file mode 100644 index 00000000000..0bd1a85e371 --- /dev/null +++ b/internal/core/runtime/testdata/basic.txtar @@ -0,0 +1,30 @@ +-- cue.mod/modules.cue -- +-- file1.cue -- +@extern("test") + +package foo + + +Foo: _ @extern(file1.xx, abi=c, sig="func(int)int") + +Rename: _ @extern(file1.xx, name=Emaner, abi=c, sig="func(int)int") + +-- file2.cue -- +@extern("test") + +package foo + + +Bar: { + @other() + @extern(file2.xx, abi=c, sig="func(int)int") + _ +} + +-- extern/out -- +-- out/extern -- +{ + Foo: implFoo1 + Bar: implBar2 + Rename: implEmaner1 +} diff --git a/internal/core/runtime/testdata/compile.txtar b/internal/core/runtime/testdata/compile.txtar new file mode 100644 index 00000000000..d78841da4ed --- /dev/null +++ b/internal/core/runtime/testdata/compile.txtar @@ -0,0 +1,8 @@ +-- compile.cue -- +@extern("test") + +package ok + +foo: _ @extern("file.xx", fail) +-- out/extern -- +TEST: fail compilation diff --git a/internal/core/runtime/testdata/errors.txtar b/internal/core/runtime/testdata/errors.txtar new file mode 100644 index 00000000000..145c614e3d3 --- /dev/null +++ b/internal/core/runtime/testdata/errors.txtar @@ -0,0 +1,113 @@ +-- cue.mod/modules.cue -- + +-- invalid_file_attr.cue -- +@extern("test" foo) + +package foo + +-- invalid_field_attr.cue -- + +@extern("test") + +// Foo + +package foo + +Fn1: _ @extern("file1.xx" abi sig) + +-- empty_extern.cue -- +@extern() + +package foo + +Fn2: _ @extern("file1.xx" abi sig) + + +-- unknown_interpreter.cue -- +@extern("wazem") + +package foo + +Fn3: _ @extern("file1.xx" abi sig) + +-- double_extern_a.cue -- +@extern("test") +@extern("test") + +package foo + +Fn4a: _ @extern("file1.xx") + +-- double_extern_b.cue -- +@extern("test") +@extern("test") +@extern("test") + +package foo + +Fn4b: _ @extern("file1.xx") + +-- package_attr.cue -- +@extern("test") + +package foo + +@extern("file1.xx") +Fn5: _ + +-- duplicate.cue -- +@extern("test") + +package foo + + +Fn6: _ @extern("file1.xx",sig=func(int)int) @extern("file1.xx", sig=func(int)bool) + +Fn7: { + @extern("file1.xx",sig=func(int)int) + _ +} @extern("file1.xx", sig=func(int)bool) + +-- non_ident.cue -- +@extern("test") + +package foo + + +"a-b": _ @extern("file1.xx",sig=func(int)int) + +[string]: _ @extern("file1.xx",sig=func(int)int) + +-- late_extern.cue -- +package foo + +@extern("test") + + +Foo: _ @extern(file1.xx, abi=c, sig="func(int)int") + +-- out/extern -- +only one file-level extern attribute allowed per file: + ./double_extern_a.cue:2:1 +only one file-level extern attribute allowed per file: + ./double_extern_b.cue:2:1 +duplicate extern attributes: + ./duplicate.cue:6:45 +duplicate extern attributes: + ./duplicate.cue:11:3 +interpreter name must be non-empty: + ./empty_extern.cue:1:1 +invalid attribute: expected comma: + ./invalid_field_attr.cue:8:8 +invalid attribute: expected comma: + ./invalid_file_attr.cue:1:1 +extern attribute must appear before package clause: + ./late_extern.cue:3:1 +can only define functions for fields with identifier names, found "a-b": + ./non_ident.cue:6:10 +can only define functions for fields with identifier names, found [string]: + ./non_ident.cue:8:13 +extern attribute not associated with field: + ./package_attr.cue:5:1 +no interpreter defined for "wazem": + ./unknown_interpreter.cue:1:1 diff --git a/internal/core/runtime/testdata/failinit.txtar b/internal/core/runtime/testdata/failinit.txtar new file mode 100644 index 00000000000..54ecee8e3be --- /dev/null +++ b/internal/core/runtime/testdata/failinit.txtar @@ -0,0 +1,12 @@ +-- init.cue -- +@extern("test") + +// Package name failinit is used as a marker in the test code to fail +// initialization. +package failinit + + +foo: _ @extern("file.xx") + +-- out/extern -- +TEST: fail initialization diff --git a/internal/core/runtime/testdata/nested.txtar b/internal/core/runtime/testdata/nested.txtar new file mode 100644 index 00000000000..76ee8afff80 --- /dev/null +++ b/internal/core/runtime/testdata/nested.txtar @@ -0,0 +1,34 @@ +-- cue.mod/modules.cue -- +-- file1.cue -- +@extern("test") + +package foo + + +a: Foo: _ @extern(file1.xx, abi=c, sig="func(int)int") + +a: Rename: _ @extern(file1.xx, name=Emaner, abi=c, sig="func(int)int") + +-- file2.cue -- +@extern("test") + +package foo + + +a: foo: Bar: { + @other() + @extern(file2.xx, abi=c, sig="func(int)int") + _ +} + +-- extern/out -- +-- out/extern -- +{ + a: { + Foo: implFoo1 + Rename: implEmaner1 + foo: { + Bar: implBar2 + } + } +} diff --git a/internal/core/runtime/testdata/no_top_extern.txtar b/internal/core/runtime/testdata/no_top_extern.txtar new file mode 100644 index 00000000000..0bbc758ddf0 --- /dev/null +++ b/internal/core/runtime/testdata/no_top_extern.txtar @@ -0,0 +1,21 @@ +// TODO: We do not generate an error here as it allows files to be processed +// faster. But maybe it is more user-friendly to report the error. +-- cue.mod/modules.cue -- +-- in.cue -- +// Missing @extern("test") +@dummy() + +package foo + + +Foo: _ @extern(file.xx, abi=c, sig="func(int)int") + +-- extern/out -- +-- out/extern/config -- +{ + Foo: _ +} +-- out/extern -- +{ + Foo: _ +} diff --git a/internal/core/runtime/testdata/none.txtar b/internal/core/runtime/testdata/none.txtar new file mode 100644 index 00000000000..da6fcb8b628 --- /dev/null +++ b/internal/core/runtime/testdata/none.txtar @@ -0,0 +1,7 @@ +-- cue.mod/modules.cue -- +-- in.cue -- +a: 2 +-- out/extern -- +{ + a: 2 +} diff --git a/internal/core/runtime/testdata/nopkg.txtar b/internal/core/runtime/testdata/nopkg.txtar new file mode 100644 index 00000000000..108b7341f91 --- /dev/null +++ b/internal/core/runtime/testdata/nopkg.txtar @@ -0,0 +1,10 @@ +-- cue.mod/modules.cue -- +-- no_package.cue -- +@extern("test") + + +Foo: _ @extern(file1.xx, abi=c, sig="func(int)int") + +-- out/extern -- +extern attribute without package clause: + ./no_package.cue:1:1 diff --git a/internal/core/runtime/testdata/nullinit.txtar b/internal/core/runtime/testdata/nullinit.txtar new file mode 100644 index 00000000000..1a35f0ef041 --- /dev/null +++ b/internal/core/runtime/testdata/nullinit.txtar @@ -0,0 +1,14 @@ +-- init.cue -- +@extern("test") + +// Package name failinit is used as a marker in the test code to indicate +// there is nothing to do. +package nullinit + + +foo: _ @extern("file.xx") + +-- out/extern -- +{ + foo: _ +} diff --git a/internal/core/walk/walk.go b/internal/core/walk/walk.go index 0d39e13456b..ad53b0c7682 100644 --- a/internal/core/walk/walk.go +++ b/internal/core/walk/walk.go @@ -30,9 +30,15 @@ func Features(x adt.Expr, f func(label adt.Feature, src adt.Node)) { w.Elem(x) } +// A Visitor walks over all elements in an ADT, recursively. type Visitor struct { + // Feature is invoked for all field names. Feature func(f adt.Feature, src adt.Node) - Before func(adt.Node) bool + + // Before is invoked for all invoked for all nodes in pre-order traversal. + // Return false prevents the visitor from visiting the nodes descendant + // elements. + Before func(adt.Node) bool } func (w *Visitor) Elem(x adt.Elem) {