blob: 3abd5fcf871a26190a15e7ed86fe263795a2bc84 [file] [log] [blame]
// 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 (
goast "go/ast"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/build"
"cuelang.org/go/cue/errors"
"cuelang.org/go/cue/token"
"cuelang.org/go/internal"
)
// An Instance defines a single configuration based on a collection of
// underlying CUE files.
type Instance struct {
*index
rootStruct *structLit // the struct to insert root values into
rootValue value // the value to evaluate: may add comprehensions
// scope is used as an additional top-level scope between the package scope
// and the predeclared identifiers.
scope *structLit
ImportPath string
Dir string
PkgName string
DisplayName string
Incomplete bool // true if Pkg and all its dependencies are free of errors
Err errors.Error // non-nil if the package had errors
inst *build.Instance
complete bool // for cycle detection
}
func (x *index) addInst(p *Instance) *Instance {
if p.rootStruct == nil {
panic("struct must not be nil")
}
p.index = x
x.imports[p.rootStruct] = p
if p.ImportPath != "" {
x.importsByPath[p.ImportPath] = p
}
return p
}
func (x *index) getImportFromNode(v value) *Instance {
imp := x.imports[v]
if imp == nil && x.parent != nil {
return x.parent.getImportFromNode(v)
}
return imp
}
func init() {
internal.MakeInstance = func(value interface{}) interface{} {
v := value.(Value)
x := v.eval(v.ctx())
st, ok := x.(*structLit)
if !ok {
st = &structLit{baseValue: x.base(), emit: x}
}
return v.ctx().index.addInst(&Instance{
rootStruct: st,
rootValue: v.path.v,
})
}
}
// newInstance creates a new instance. Use Insert to populate the instance.
func (x *index) newInstance(p *build.Instance) *Instance {
// TODO: associate root source with structLit.
st := &structLit{baseValue: baseValue{nil}}
i := &Instance{
rootStruct: st,
rootValue: st,
inst: p,
}
if p != nil {
i.ImportPath = p.ImportPath
i.Dir = p.Dir
i.PkgName = p.PkgName
i.DisplayName = p.ImportPath
if p.Err != nil {
i.setListOrError(p.Err)
}
}
return x.addInst(i)
}
func (inst *Instance) setListOrError(err errors.Error) {
inst.Incomplete = true
inst.Err = errors.Append(inst.Err, err)
}
func (inst *Instance) setError(err errors.Error) {
inst.Incomplete = true
inst.Err = errors.Append(inst.Err, err)
}
func (inst *Instance) eval(ctx *context) evaluated {
// TODO: remove manifest here?
v := ctx.manifest(inst.rootValue)
if s, ok := v.(*structLit); ok && s.emit != nil {
e := s.emit.evalPartial(ctx)
src := binSrc(token.NoPos, opUnify, v, e)
outer:
switch e.(type) {
case *structLit, *top:
v = binOp(ctx, src, opUnifyUnchecked, v, e)
if s, ok := v.(*structLit); ok {
s.emit = nil
}
default:
for _, a := range s.arcs {
if !a.definition {
v = binOp(ctx, src, opUnify, v, e)
break outer
}
}
return e
}
}
return v
}
func init() {
internal.EvalExpr = func(value, expr interface{}) interface{} {
v := value.(Value)
e := expr.(ast.Expr)
ctx := v.idx.newContext()
return newValueRoot(ctx, evalExpr(ctx, v.eval(ctx), e))
}
}
func evalExpr(ctx *context, x value, expr ast.Expr) evaluated {
if isBottom(x) {
return ctx.mkErr(x, "error evaluating instance: %v", x)
}
obj, ok := x.(*structLit)
if !ok {
return ctx.mkErr(x, "instance is not a struct, found %s", x.kind())
}
v := newVisitor(ctx.index, nil, nil, obj, true)
return v.walk(expr).evalPartial(ctx)
}
// Doc returns the package comments for this instance.
func (inst *Instance) Doc() []*ast.CommentGroup {
var docs []*ast.CommentGroup
if inst.inst == nil {
return nil
}
for _, f := range inst.inst.Files {
pkg, _, _ := internal.PackageInfo(f)
if pkg == nil {
continue
}
var cg *ast.CommentGroup
for _, c := range pkg.Comments() {
if c.Position == 0 {
cg = c
}
}
if cg != nil {
docs = append(docs, cg)
}
}
return docs
}
// Value returns the root value of the configuration. If the configuration
// defines in emit value, it will be that value. Otherwise it will be all
// top-level values.
func (inst *Instance) Value() Value {
ctx := inst.newContext()
return newValueRoot(ctx, inst.eval(ctx))
}
// Eval evaluates an expression within an existing instance.
//
// Expressions may refer to builtin packages if they can be uniquely identified.
func (inst *Instance) Eval(expr ast.Expr) Value {
ctx := inst.newContext()
result := evalExpr(ctx, inst.eval(ctx), expr)
return newValueRoot(ctx, result)
}
// Merge unifies the given instances into a single one.
//
// Errors regarding conflicts are included in the result, but not reported, so
// that these will only surface during manifestation. This allows
// non-conflicting parts to be used.
func Merge(inst ...*Instance) *Instance {
switch len(inst) {
case 0:
return nil
case 1:
return inst[0]
}
values := []value{}
for _, i := range inst {
if i.Err != nil {
return i
}
values = append(values, i.rootValue)
}
merged := &mergedValues{values: values}
ctx := inst[0].newContext()
st, ok := ctx.manifest(merged).(*structLit)
if !ok {
return nil
}
p := ctx.index.addInst(&Instance{
rootStruct: st,
rootValue: merged,
complete: true,
})
return p
}
// Build creates a new instance from the build instances, allowing unbound
// identifier to bind to the top-level field in inst. The top-level fields in
// inst take precedence over predeclared identifier and builtin functions.
func (inst *Instance) Build(p *build.Instance) *Instance {
p.Complete()
idx := inst.index
i := idx.newInstance(p)
if i.Err != nil {
return i
}
ctx := inst.newContext()
val := newValueRoot(ctx, inst.rootValue)
v, err := val.structValFull(ctx)
if err != nil {
i.setError(val.toErr(err))
return i
}
i.scope = v.obj
if err := resolveFiles(idx, p); err != nil {
i.setError(err)
return i
}
for _, f := range p.Files {
if err := i.insertFile(f); err != nil {
i.setListOrError(err)
}
}
i.complete = true
return i
}
// Lookup reports the value at a path starting from the top level struct (not
// the emitted value). The Exists method of the returned value will report false
// if the path did not exist. The Err method reports if any error occurred
// during evaluation. The empty path returns the top-level configuration struct,
// regardless of whether an emit value was specified.
// Use LookupDef for definitions or LookupField for any kind of field.
func (inst *Instance) Lookup(path ...string) Value {
idx := inst.index
ctx := idx.newContext()
v := newValueRoot(ctx, inst.rootValue)
for _, k := range path {
obj, err := v.structValData(ctx)
if err != nil {
return Value{idx, &valueData{arc: arc{cache: err, v: err}}}
}
v = obj.Lookup(k)
}
return v
}
// LookupDef reports the definition with the given name within struct v. The
// Exists method of the returned value will report false if the definition did
// not exist. The Err method reports if any error occurred during evaluation.
func (inst *Instance) LookupDef(path string) Value {
ctx := inst.index.newContext()
v := newValueRoot(ctx, inst.rootValue.evalPartial(ctx))
return v.LookupDef(path)
}
// LookupField reports a Field at a path starting from v, or an error if the
// path is not. The empty path returns v itself.
//
// It cannot look up hidden or unexported fields.
func (inst *Instance) LookupField(path ...string) (f FieldInfo, err error) {
idx := inst.index
ctx := idx.newContext()
v := newValueRoot(ctx, inst.rootValue)
for i, k := range path {
s, err := v.Struct()
if err != nil {
return f, err
}
f, err = s.FieldByName(k)
if err != nil {
return f, err
}
if f.IsHidden || (i == 0 || f.IsDefinition) && !goast.IsExported(f.Name) {
return f, errNotFound
}
v = f.Value
}
return f, err
}
// Fill creates a new instance with the values of the old instance unified with
// the given value. It is not possible to update the emit value.
//
// Values may be any Go value that can be converted to CUE, an ast.Expr or
// a Value. In the latter case, it will panic if the Value is not from the same
// Runtime.
func (inst *Instance) Fill(x interface{}, path ...string) (*Instance, error) {
ctx := inst.newContext()
root := ctx.manifest(inst.rootValue)
for i := len(path) - 1; i >= 0; i-- {
x = map[string]interface{}{path[i]: x}
}
var value evaluated
if v, ok := x.(Value); ok {
if inst.index != v.ctx().index {
panic("value of type Value is not created with same Runtime as Instance")
}
value = v.eval(ctx)
} else {
value = convert(ctx, root, true, x)
}
eval := binOp(ctx, baseValue{}, opUnify, root, value)
// TODO: validate recursively?
err := inst.Err
var st *structLit
var stVal evaluated
switch x := eval.(type) {
case *structLit:
st = x
stVal = x
default:
// This should not happen.
b := ctx.mkErr(eval, "error filling struct")
err = inst.Value().toErr(b)
st = &structLit{emit: b}
stVal = b
case *bottom:
err = inst.Value().toErr(x)
st = &structLit{emit: x}
stVal = x
}
inst = inst.index.addInst(&Instance{
rootStruct: st,
rootValue: stVal,
inst: nil,
// Omit ImportPath to indicate this is not an importable package.
Dir: inst.Dir,
PkgName: inst.PkgName,
Incomplete: inst.Incomplete,
Err: err,
complete: err != nil,
})
return inst, err
}