| // Copyright 2018 The 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 cue |
| |
| import ( |
| "cuelang.org/go/cue/ast" |
| "cuelang.org/go/cue/ast/astutil" |
| "cuelang.org/go/cue/build" |
| "cuelang.org/go/cue/errors" |
| "cuelang.org/go/cue/token" |
| "cuelang.org/go/internal/core/adt" |
| "cuelang.org/go/internal/core/compile" |
| "cuelang.org/go/internal/core/convert" |
| "cuelang.org/go/internal/core/debug" |
| "cuelang.org/go/internal/core/eval" |
| "cuelang.org/go/internal/core/runtime" |
| ) |
| |
| // A Context is used for creating CUE Values. |
| // |
| // A Context keeps track of loaded instances, indices of internal |
| // representations of values, and defines the set of supported builtins. Any |
| // operation that involves two Values should originate from the same Context. |
| // |
| // Use |
| // |
| // ctx := cuecontext.New() |
| // |
| // to create a new Context. |
| type Context runtime.Runtime |
| |
| func (c *Context) runtime() *runtime.Runtime { |
| rt := (*runtime.Runtime)(c) |
| return rt |
| } |
| |
| func (c *Context) ctx() *adt.OpContext { |
| return newContext(c.runtime()) |
| } |
| |
| // Context reports the Context with which this value was created. |
| func (v Value) Context() *Context { |
| return (*Context)(v.idx) |
| } |
| |
| // A BuildOption defines options for the various build-related methods of |
| // Context. |
| type BuildOption func(o *runtime.Config) |
| |
| // Scope defines a context in which to resolve unresolved identifiers. |
| // |
| // Only one scope may be given. It panics if more than one scope is given |
| // or if the Context in which scope was created differs from the one where |
| // this option is used. |
| func Scope(scope Value) BuildOption { |
| return func(o *runtime.Config) { |
| if o.Runtime != scope.idx { |
| panic("incompatible runtime") |
| } |
| if o.Scope != nil { |
| panic("more than one scope is given") |
| } |
| o.Scope = valueScope(scope) |
| } |
| } |
| |
| // Filename assigns a filename to parsed content. |
| func Filename(filename string) BuildOption { |
| return func(o *runtime.Config) { o.Filename = filename } |
| } |
| |
| // ImportPath defines the import path to use for building CUE. The import path |
| // influences the scope in which identifiers occurring in the input CUE are |
| // defined. Passing the empty string is equal to not specifying this option. |
| // |
| // This option is typically not necessary when building using a build.Instance, |
| // but takes precedence otherwise. |
| func ImportPath(path string) BuildOption { |
| return func(o *runtime.Config) { o.ImportPath = path } |
| } |
| |
| // InferBuiltins allows unresolved references to bind to builtin packages with a |
| // unique package name. |
| // |
| // This option is intended for evaluating expressions in a context where import |
| // statements cannot be used. It is not recommended to use this for evaluating |
| // CUE files. |
| func InferBuiltins(elide bool) BuildOption { |
| return func(o *runtime.Config) { |
| o.Imports = func(x *ast.Ident) (pkgPath string) { |
| return o.Runtime.BuiltinPackagePath(x.Name) |
| } |
| } |
| } |
| |
| func (c *Context) parseOptions(options []BuildOption) (cfg runtime.Config) { |
| cfg.Runtime = (*runtime.Runtime)(c) |
| for _, f := range options { |
| f(&cfg) |
| } |
| return cfg |
| } |
| |
| // BuildInstance creates a Value from the given build.Instance. |
| // |
| // The returned Value will represent an error, accessible through Err, if any |
| // error occurred. |
| func (c *Context) BuildInstance(i *build.Instance, options ...BuildOption) Value { |
| cfg := c.parseOptions(options) |
| v, err := c.runtime().Build(&cfg, i) |
| if err != nil { |
| return c.makeError(err) |
| } |
| return c.make(v) |
| } |
| |
| func (c *Context) makeError(err errors.Error) Value { |
| b := &adt.Bottom{Err: err} |
| node := &adt.Vertex{BaseValue: b} |
| node.UpdateStatus(adt.Finalized) |
| node.AddConjunct(adt.MakeRootConjunct(nil, b)) |
| return c.make(node) |
| } |
| |
| // BuildInstances creates a Value for each of the given instances and reports |
| // the combined errors or nil if there were no errors. |
| func (c *Context) BuildInstances(instances []*build.Instance) ([]Value, error) { |
| var errs errors.Error |
| var a []Value |
| for _, b := range instances { |
| v, err := c.runtime().Build(nil, b) |
| if err != nil { |
| errs = errors.Append(errs, err) |
| } |
| a = append(a, c.make(v)) |
| } |
| return a, errs |
| } |
| |
| // BuildFile creates a Value from f. |
| // |
| // The returned Value will represent an error, accessible through Err, if any |
| // error occurred. |
| func (c *Context) BuildFile(f *ast.File, options ...BuildOption) Value { |
| cfg := c.parseOptions(options) |
| return c.compile(c.runtime().CompileFile(&cfg, f)) |
| } |
| |
| func (c *Context) compile(v *adt.Vertex, p *build.Instance) Value { |
| if p.Err != nil { |
| return c.makeError(p.Err) |
| } |
| return c.make(v) |
| } |
| |
| // BuildExpr creates a Value from x. |
| // |
| // The returned Value will represent an error, accessible through Err, if any |
| // error occurred. |
| func (c *Context) BuildExpr(x ast.Expr, options ...BuildOption) Value { |
| r := c.runtime() |
| cfg := c.parseOptions(options) |
| |
| ctx := c.ctx() |
| |
| // TODO: move to runtime?: it probably does not make sense to treat BuildExpr |
| // and the expression resulting from CompileString differently. |
| astutil.ResolveExpr(x, errFn) |
| |
| pkgPath := cfg.ImportPath |
| if pkgPath == "" { |
| pkgPath = anonymousPkg |
| } |
| |
| conjunct, err := compile.Expr(&cfg.Config, r, pkgPath, x) |
| if err != nil { |
| return c.makeError(err) |
| } |
| v := adt.Resolve(ctx, conjunct) |
| |
| return c.make(v) |
| } |
| |
| func errFn(pos token.Pos, msg string, args ...interface{}) {} |
| |
| // resolveExpr binds unresolved expressions to values in the expression or v. |
| func resolveExpr(ctx *adt.OpContext, v Value, x ast.Expr) adt.Value { |
| cfg := &compile.Config{Scope: valueScope(v)} |
| |
| astutil.ResolveExpr(x, errFn) |
| |
| c, err := compile.Expr(cfg, ctx, anonymousPkg, x) |
| if err != nil { |
| return &adt.Bottom{Err: err} |
| } |
| return adt.Resolve(ctx, c) |
| } |
| |
| // anonymousPkg reports a package path that can never resolve to a valid package. |
| const anonymousPkg = "_" |
| |
| // CompileString parses and build a Value from the given source string. |
| // |
| // The returned Value will represent an error, accessible through Err, if any |
| // error occurred. |
| func (c *Context) CompileString(src string, options ...BuildOption) Value { |
| cfg := c.parseOptions(options) |
| return c.compile(c.runtime().Compile(&cfg, src)) |
| } |
| |
| // ParseString parses and build a Value from the given source bytes. |
| // |
| // The returned Value will represent an error, accessible through Err, if any |
| // error occurred. |
| func (c *Context) CompileBytes(b []byte, options ...BuildOption) Value { |
| cfg := c.parseOptions(options) |
| return c.compile(c.runtime().Compile(&cfg, b)) |
| } |
| |
| // TODO: fs.FS or custom wrapper? |
| // // CompileFile parses and build a Value from the given source bytes. |
| // // |
| // // The returned Value will represent an error, accessible through Err, if any |
| // // error occurred. |
| // func (c *Context) CompileFile(f fs.File, options ...BuildOption) Value { |
| // b, err := io.ReadAll(f) |
| // if err != nil { |
| // return c.makeError(errors.Promote(err, "parsing file system file")) |
| // } |
| // return c.compile(c.runtime().Compile("", b)) |
| // } |
| |
| func (c *Context) make(v *adt.Vertex) Value { |
| return newValueRoot(c.runtime(), newContext(c.runtime()), v) |
| } |
| |
| // An EncodeOption defines options for the various encoding-related methods of |
| // Context. |
| type EncodeOption func(*encodeOptions) |
| |
| type encodeOptions struct { |
| nilIsTop bool |
| } |
| |
| func (o *encodeOptions) process(option []EncodeOption) { |
| for _, f := range option { |
| f(o) |
| } |
| } |
| |
| // NilIsAny indicates whether a nil value is interpreted as null or _. |
| // |
| // The default is to interpret nil as _. |
| func NilIsAny(isAny bool) EncodeOption { |
| return func(o *encodeOptions) { o.nilIsTop = isAny } |
| } |
| |
| // Encode converts a Go value to a CUE value. |
| // |
| // The returned Value will represent an error, accessible through Err, if any |
| // error occurred. |
| // |
| // Encode traverses the value v recursively. If an encountered value implements |
| // the json.Marshaler interface and is not a nil pointer, Encode calls its |
| // MarshalJSON method to produce JSON and convert that to CUE instead. If no |
| // MarshalJSON method is present but the value implements encoding.TextMarshaler |
| // instead, Encode calls its MarshalText method and encodes the result as a |
| // string. |
| // |
| // Otherwise, Encode uses the following type-dependent default encodings: |
| // |
| // Boolean values encode as CUE booleans. |
| // |
| // Floating point, integer, and *big.Int and *big.Float values encode as CUE |
| // numbers. |
| // |
| // String values encode as CUE strings coerced to valid UTF-8, replacing |
| // sequences of invalid bytes with the Unicode replacement rune as per Unicode's |
| // and W3C's recommendation. |
| // |
| // Array and slice values encode as CUE lists, except that []byte encodes as a |
| // bytes value, and a nil slice encodes as the null. |
| // |
| // Struct values encode as CUE structs. Each exported struct field becomes a |
| // member of the object, using the field name as the object key, unless the |
| // field is omitted for one of the reasons given below. |
| // |
| // The encoding of each struct field can be customized by the format string |
| // stored under the "json" key in the struct field's tag. The format string |
| // gives the name of the field, possibly followed by a comma-separated list of |
| // options. The name may be empty in order to specify options without overriding |
| // the default field name. |
| // |
| // The "omitempty" option specifies that the field should be omitted from the |
| // encoding if the field has an empty value, defined as false, 0, a nil pointer, |
| // a nil interface value, and any empty array, slice, map, or string. |
| // |
| // See the documentation for Go's json.Marshal for more details on the field |
| // tags and their meaning. |
| // |
| // Anonymous struct fields are usually encoded as if their inner exported |
| // fields were fields in the outer struct, subject to the usual Go visibility |
| // rules amended as described in the next paragraph. An anonymous struct field |
| // with a name given in its JSON tag is treated as having that name, rather than |
| // being anonymous. An anonymous struct field of interface type is treated the |
| // same as having that type as its name, rather than being anonymous. |
| // |
| // The Go visibility rules for struct fields are amended for when deciding which |
| // field to encode or decode. If there are multiple fields at the same level, |
| // and that level is the least nested (and would therefore be the nesting level |
| // selected by the usual Go rules), the following extra rules apply: |
| // |
| // 1) Of those fields, if any are JSON-tagged, only tagged fields are |
| // considered, even if there are multiple untagged fields that would otherwise |
| // conflict. |
| // |
| // 2) If there is exactly one field (tagged or not according to the first rule), |
| // that is selected. |
| // |
| // 3) Otherwise there are multiple fields, and all are ignored; no error occurs. |
| // |
| // Map values encode as CUE structs. The map's key type must either be a string, |
| // an integer type, or implement encoding.TextMarshaler. The map keys are sorted |
| // and used as CUE struct field names by applying the following rules, subject |
| // to the UTF-8 coercion described for string values above: |
| // |
| // - keys of any string type are used directly |
| // - encoding.TextMarshalers are marshaled |
| // - integer keys are converted to strings |
| // |
| // Pointer values encode as the value pointed to. A nil pointer encodes as the |
| // null CUE value. |
| // |
| // Interface values encode as the value contained in the interface. A nil |
| // interface value encodes as the null CUE value. The NilIsAny EncodingOption |
| // can be used to interpret nil as any (_) instead. |
| // |
| // Channel, complex, and function values cannot be encoded in CUE. Attempting to |
| // encode such a value results in the returned value being an error, accessible |
| // through the Err method. |
| // |
| func (c *Context) Encode(x interface{}, option ...EncodeOption) Value { |
| switch v := x.(type) { |
| case adt.Value: |
| return newValueRoot(c.runtime(), c.ctx(), v) |
| } |
| var options encodeOptions |
| options.process(option) |
| |
| ctx := c.ctx() |
| // TODO: is true the right default? |
| expr := convert.GoValueToValue(ctx, x, options.nilIsTop) |
| n := &adt.Vertex{} |
| n.AddConjunct(adt.MakeRootConjunct(nil, expr)) |
| n.Finalize(ctx) |
| return c.make(n) |
| } |
| |
| // Encode converts a Go type to a CUE value. |
| // |
| // The returned Value will represent an error, accessible through Err, if any |
| // error occurred. |
| func (c *Context) EncodeType(x interface{}, option ...EncodeOption) Value { |
| switch v := x.(type) { |
| case *adt.Vertex: |
| return c.make(v) |
| } |
| |
| ctx := c.ctx() |
| expr, err := convert.GoTypeToExpr(ctx, x) |
| if err != nil { |
| return c.makeError(err) |
| } |
| n := &adt.Vertex{} |
| n.AddConjunct(adt.MakeRootConjunct(nil, expr)) |
| n.Finalize(ctx) |
| return c.make(n) |
| } |
| |
| // NewList creates a Value that is a list of the given values. |
| // |
| // All Values must be created by c. |
| func (c *Context) NewList(v ...Value) Value { |
| a := make([]adt.Value, len(v)) |
| for i, x := range v { |
| if x.idx != (*runtime.Runtime)(c) { |
| panic("values must be from same Context") |
| } |
| a[i] = x.v |
| } |
| return c.make(c.ctx().NewList(a...)) |
| } |
| |
| // TODO: |
| |
| // func (c *Context) NewExpr(op Op, v ...Value) Value { |
| // return Value{} |
| // } |
| |
| // func (c *Context) NewValue(v ...ValueElem) Value { |
| // return Value{} |
| // } |
| |
| // func NewAttr(key string, values ...string) *Attribute { |
| // return &Attribute{} |
| // } |
| |
| // // Clear unloads all previously-loaded imports. |
| // func (c *Context) Clear() { |
| // } |
| |
| // // Values created up to the point of the Fork will be valid in both runtimes. |
| // func (c *Context) Fork() *Context { |
| // return nil |
| // } |
| |
| // type ValueElem interface { |
| // } |
| |
| // func NewField(sel Selector, value Value, attrs ...Attribute) ValueElem { |
| // return nil |
| // } |
| |
| // func NewDocComment(text string) ValueElem { |
| // return nil |
| // } |
| |
| // newContext returns a new evaluation context. |
| func newContext(idx *runtime.Runtime) *adt.OpContext { |
| if idx == nil { |
| return nil |
| } |
| return eval.NewContext(idx, nil) |
| } |
| |
| func debugStr(ctx *adt.OpContext, v adt.Node) string { |
| return debug.NodeString(ctx, v, nil) |
| } |
| |
| func str(c *adt.OpContext, v adt.Node) string { |
| return debugStr(c, v) |
| } |
| |
| // eval returns the evaluated value. This may not be the vertex. |
| // |
| // Deprecated: use ctx.value |
| func (v Value) eval(ctx *adt.OpContext) adt.Value { |
| if v.v == nil { |
| panic("undefined value") |
| } |
| x := manifest(ctx, v.v) |
| return x.Value() |
| } |
| |
| // TODO: change from Vertex to Vertex. |
| func manifest(ctx *adt.OpContext, v *adt.Vertex) *adt.Vertex { |
| v.Finalize(ctx) |
| return v |
| } |