| // Copyright 2020 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 adt |
| |
| import ( |
| "fmt" |
| "log" |
| "os" |
| "reflect" |
| "regexp" |
| |
| "github.com/cockroachdb/apd/v2" |
| "golang.org/x/text/encoding/unicode" |
| |
| "cuelang.org/go/cue/ast" |
| "cuelang.org/go/cue/errors" |
| "cuelang.org/go/cue/format" |
| "cuelang.org/go/cue/token" |
| ) |
| |
| // Debug sets whether extra aggressive checking should be done. |
| // This should typically default to true for pre-releases and default to |
| // false otherwise. |
| var Debug bool = os.Getenv("CUE_DEBUG") != "0" |
| |
| // Verbosity sets the log level. There are currently only two levels: |
| // 0: no logging |
| // 1: logging |
| var Verbosity int |
| |
| // Assert panics if the condition is false. Assert can be used to check for |
| // conditions that are considers to break an internal variant or unexpected |
| // condition, but that nonetheless probably will be handled correctly down the |
| // line. For instance, a faulty condition could lead to to error being caught |
| // down the road, but resulting in an inaccurate error message. In production |
| // code it is better to deal with the bad error message than to panic. |
| // |
| // It is advisable for each use of Assert to document how the error is expected |
| // to be handled down the line. |
| func Assertf(b bool, format string, args ...interface{}) { |
| if Debug && !b { |
| panic(fmt.Sprintf("assertion failed: "+format, args...)) |
| } |
| } |
| |
| // Assertf either panics or reports an error to c if the condition is not met. |
| func (c *OpContext) Assertf(pos token.Pos, b bool, format string, args ...interface{}) { |
| if !b { |
| if Debug { |
| panic(fmt.Sprintf("assertion failed: "+format, args...)) |
| } |
| c.addErrf(0, pos, format, args...) |
| } |
| } |
| |
| func init() { |
| log.SetFlags(log.Lshortfile) |
| } |
| |
| func Logf(format string, args ...interface{}) { |
| if Verbosity == 0 { |
| return |
| } |
| s := fmt.Sprintf(format, args...) |
| _ = log.Output(2, s) |
| } |
| |
| var pMap = map[*Vertex]int{} |
| |
| func (c *OpContext) Logf(v *Vertex, format string, args ...interface{}) { |
| if Verbosity == 0 { |
| return |
| } |
| p := pMap[v] |
| if p == 0 { |
| p = len(pMap) + 1 |
| pMap[v] = p |
| } |
| a := append([]interface{}{ |
| p, |
| v.Label.SelectorString(c), |
| v.Path(), |
| }, args...) |
| for i := 2; i < len(a); i++ { |
| switch x := a[i].(type) { |
| case Node: |
| a[i] = c.Str(x) |
| case Feature: |
| a[i] = x.SelectorString(c) |
| } |
| } |
| s := fmt.Sprintf(" [%d] %s/%v"+format, a...) |
| _ = log.Output(2, s) |
| } |
| |
| // Runtime defines an interface for low-level representation conversion and |
| // lookup. |
| type Runtime interface { |
| // StringIndexer allows for converting string labels to and from a |
| // canonical numeric representation. |
| StringIndexer |
| |
| // LoadImport loads a unique Vertex associated with a given import path. It |
| // returns an error if no import for this package could be found. |
| LoadImport(importPath string) (*Vertex, errors.Error) |
| |
| // StoreType associates a CUE expression with a Go type. |
| StoreType(t reflect.Type, src ast.Expr, expr Expr) |
| |
| // LoadType retrieves a previously stored CUE expression for a given Go |
| // type if available. |
| LoadType(t reflect.Type) (src ast.Expr, expr Expr, ok bool) |
| } |
| |
| type Config struct { |
| Runtime |
| Format func(Node) string |
| } |
| |
| // New creates an operation context. |
| func New(v *Vertex, cfg *Config) *OpContext { |
| if cfg.Runtime == nil { |
| panic("nil Runtime") |
| } |
| ctx := &OpContext{ |
| Runtime: cfg.Runtime, |
| Format: cfg.Format, |
| vertex: v, |
| } |
| if v != nil { |
| ctx.e = &Environment{Up: nil, Vertex: v} |
| } |
| return ctx |
| } |
| |
| // An OpContext implements CUE's unification operation. It's operations only |
| // operation on values that are created with the Runtime with which an OpContext |
| // is associated. An OpContext is not goroutine save and only one goroutine may |
| // use an OpContext at a time. |
| // |
| type OpContext struct { |
| Runtime |
| Format func(Node) string |
| |
| stats Stats |
| freeListNode *nodeContext |
| |
| e *Environment |
| src ast.Node |
| errs *Bottom |
| positions []Node // keep track of error positions |
| |
| // vertex is used to determine the path location in case of error. Turning |
| // this into a stack could also allow determining the cyclic path for |
| // structural cycle errors. |
| vertex *Vertex |
| |
| nonMonotonicLookupNest int32 |
| nonMonotonicRejectNest int32 |
| nonMonotonicInsertNest int32 |
| nonMonotonicGeneration int32 |
| |
| // These fields are used associate scratch fields for computing closedness |
| // of a Vertex. These fields could have been included in StructInfo (like |
| // Tomabechi's unification algorithm), but we opted for an indirection to |
| // allow concurrent unification. |
| // |
| // TODO(perf): have two generations: one for each pass of the closedness |
| // algorithm, so that the results of the first pass can be reused for all |
| // features of a node. |
| generation int |
| closed map[*closeInfo]*closeStats |
| todo *closeStats |
| |
| // inDisjunct indicates that non-monotonic checks should be skipped. |
| // This is used if we want to do some extra work to eliminate disjunctions |
| // early. The result of unificantion should be thrown away if this check is |
| // used. |
| // |
| // TODO: replace this with a mechanism to determine the correct set (per |
| // conjunct) of StructInfos to include in closedness checking. |
| inDisjunct int |
| |
| // inConstaint overrides inDisjunct as field matching should always be |
| // enabled. |
| inConstraint int |
| } |
| |
| func (n *nodeContext) skipNonMonotonicChecks() bool { |
| if n.ctx.inConstraint > 0 { |
| return false |
| } |
| return n.ctx.inDisjunct > 0 |
| } |
| |
| // Impl is for internal use only. This will go. |
| func (c *OpContext) Impl() Runtime { |
| return c.Runtime |
| } |
| |
| func (c *OpContext) Pos() token.Pos { |
| if c.src == nil { |
| return token.NoPos |
| } |
| return c.src.Pos() |
| } |
| |
| func (c *OpContext) Source() ast.Node { |
| return c.src |
| } |
| |
| // NewContext creates an operation context. |
| func NewContext(r Runtime, v *Vertex) *OpContext { |
| return New(v, &Config{Runtime: r}) |
| } |
| |
| func (c *OpContext) pos() token.Pos { |
| if c.src == nil { |
| return token.NoPos |
| } |
| return c.src.Pos() |
| } |
| |
| func (c *OpContext) spawn(node *Vertex) *Environment { |
| node.Parent = c.e.Vertex // TODO: Is this necessary? |
| return &Environment{ |
| Up: c.e, |
| Vertex: node, |
| |
| // Copy cycle data. |
| Cyclic: c.e.Cyclic, |
| Deref: c.e.Deref, |
| Cycles: c.e.Cycles, |
| } |
| } |
| |
| func (c *OpContext) Env(upCount int32) *Environment { |
| e := c.e |
| for ; upCount > 0; upCount-- { |
| e = e.Up |
| } |
| return e |
| } |
| |
| func (c *OpContext) relNode(upCount int32) *Vertex { |
| e := c.e |
| for ; upCount > 0; upCount-- { |
| e = e.Up |
| } |
| c.Unify(e.Vertex, Partial) |
| return e.Vertex |
| } |
| |
| func (c *OpContext) relLabel(upCount int32) Feature { |
| // locate current label. |
| e := c.e |
| for ; upCount > 0; upCount-- { |
| e = e.Up |
| } |
| return e.DynamicLabel |
| } |
| |
| func (c *OpContext) concreteIsPossible(op Op, x Expr) bool { |
| if !AssertConcreteIsPossible(op, x) { |
| c.AddErrf("invalid operand %s ('%s' requires concrete value)", |
| c.Str(x), op) |
| return false |
| } |
| return true |
| } |
| |
| // Assert that the given expression can evaluate to a concrete value. |
| func AssertConcreteIsPossible(op Op, x Expr) bool { |
| switch v := x.(type) { |
| case *Bottom: |
| case *BoundExpr: |
| return false |
| case Value: |
| return v.Concreteness() == Concrete |
| } |
| return true |
| } |
| |
| // HasErr reports whether any error was reported, including whether value |
| // was incomplete. |
| func (c *OpContext) HasErr() bool { |
| return c.errs != nil |
| } |
| |
| func (c *OpContext) Err() *Bottom { |
| b := c.errs |
| c.errs = nil |
| return b |
| } |
| |
| func (c *OpContext) addErrf(code ErrorCode, pos token.Pos, msg string, args ...interface{}) { |
| for i, a := range args { |
| switch x := a.(type) { |
| case Node: |
| args[i] = c.Str(x) |
| case ast.Node: |
| b, _ := format.Node(x) |
| args[i] = string(b) |
| case Feature: |
| args[i] = x.SelectorString(c.Runtime) |
| } |
| } |
| |
| err := c.NewPosf(pos, msg, args...) |
| c.addErr(code, err) |
| } |
| |
| func (c *OpContext) addErr(code ErrorCode, err errors.Error) { |
| c.AddBottom(&Bottom{Code: code, Err: err}) |
| } |
| |
| // AddBottom records an error in OpContext. |
| func (c *OpContext) AddBottom(b *Bottom) { |
| c.errs = CombineErrors(c.src, c.errs, b) |
| } |
| |
| // AddErr records an error in OpContext. It returns errors collected so far. |
| func (c *OpContext) AddErr(err errors.Error) *Bottom { |
| if err != nil { |
| c.AddBottom(&Bottom{Err: err}) |
| } |
| return c.errs |
| } |
| |
| // NewErrf creates a *Bottom value and returns it. The returned uses the |
| // current source as the point of origin of the error. |
| func (c *OpContext) NewErrf(format string, args ...interface{}) *Bottom { |
| // TODO: consider renaming ot NewBottomf: this is now confusing as we also |
| // have Newf. |
| err := c.Newf(format, args...) |
| return &Bottom{Src: c.src, Err: err, Code: EvalError} |
| } |
| |
| // AddErrf records an error in OpContext. It returns errors collected so far. |
| func (c *OpContext) AddErrf(format string, args ...interface{}) *Bottom { |
| return c.AddErr(c.Newf(format, args...)) |
| } |
| |
| type frame struct { |
| env *Environment |
| err *Bottom |
| src ast.Node |
| } |
| |
| func (c *OpContext) PushState(env *Environment, src ast.Node) (saved frame) { |
| saved.env = c.e |
| saved.err = c.errs |
| saved.src = c.src |
| |
| c.errs = nil |
| if src != nil { |
| c.src = src |
| } |
| c.e = env |
| |
| return saved |
| } |
| |
| func (c *OpContext) PopState(s frame) *Bottom { |
| err := c.errs |
| c.e = s.env |
| c.errs = s.err |
| c.src = s.src |
| return err |
| } |
| |
| // PushArc signals c that arc v is currently being processed for the purpose |
| // of error reporting. PopArc should be called with the returned value once |
| // processing of v is completed. |
| func (c *OpContext) PushArc(v *Vertex) (saved *Vertex) { |
| c.vertex, saved = v, c.vertex |
| return saved |
| } |
| |
| // PopArc signals completion of processing the current arc. |
| func (c *OpContext) PopArc(saved *Vertex) { |
| c.vertex = saved |
| } |
| |
| // Resolve finds a node in the tree. |
| // |
| // Should only be used to insert Conjuncts. TODO: perhaps only return Conjuncts |
| // and error. |
| func (c *OpContext) Resolve(env *Environment, r Resolver) (*Vertex, *Bottom) { |
| s := c.PushState(env, r.Source()) |
| |
| arc := r.resolve(c, Partial) |
| |
| err := c.PopState(s) |
| if err != nil { |
| return nil, err |
| } |
| |
| if arc.ChildErrors != nil && arc.ChildErrors.Code == StructuralCycleError { |
| return nil, arc.ChildErrors |
| } |
| |
| for { |
| x, ok := arc.BaseValue.(*Vertex) |
| if !ok { |
| break |
| } |
| arc = x |
| } |
| |
| return arc, err |
| } |
| |
| // Validate calls validates value for the given validator. |
| // |
| // TODO(errors): return boolean instead: only the caller has enough information |
| // to generate a proper error message. |
| func (c *OpContext) Validate(check Validator, value Value) *Bottom { |
| // TODO: use a position stack to push both values. |
| saved := c.src |
| c.src = check.Source() |
| |
| err := check.validate(c, value) |
| |
| c.src = saved |
| |
| return err |
| } |
| |
| // Yield evaluates a Yielder and calls f for each result. |
| func (c *OpContext) Yield(env *Environment, y Yielder, f YieldFunc) *Bottom { |
| s := c.PushState(env, y.Source()) |
| |
| y.yield(c, f) |
| |
| return c.PopState(s) |
| |
| } |
| |
| // Concrete returns the concrete value of x after evaluating it. |
| // msg is used to mention the context in which an error occurred, if any. |
| func (c *OpContext) Concrete(env *Environment, x Expr, msg interface{}) (result Value, complete bool) { |
| |
| v, complete := c.Evaluate(env, x) |
| |
| v, ok := c.getDefault(v) |
| if !ok { |
| return v, false |
| } |
| v = Unwrap(v) |
| |
| if !IsConcrete(v) { |
| complete = false |
| b := c.NewErrf("non-concrete value %v in operand to %s", c.Str(v), msg) |
| b.Code = IncompleteError |
| v = b |
| } |
| |
| if !complete { |
| return v, complete |
| } |
| |
| return v, true |
| } |
| |
| // getDefault resolves a disjunction to a single value. If there is no default |
| // value, or if there is more than one default value, it reports an "incomplete" |
| // error and return false. In all other cases it will return true, even if |
| // v is already an error. v may be nil, in which case it will also return nil. |
| func (c *OpContext) getDefault(v Value) (result Value, ok bool) { |
| var d *Disjunction |
| switch x := v.(type) { |
| default: |
| return v, true |
| |
| case *Vertex: |
| // TODO: return vertex if not disjunction. |
| switch t := x.BaseValue.(type) { |
| case *Disjunction: |
| d = t |
| |
| case *Vertex: |
| return c.getDefault(t) |
| |
| default: |
| return x, true |
| } |
| |
| case *Disjunction: |
| d = x |
| } |
| |
| if d.NumDefaults != 1 { |
| c.addErrf(IncompleteError, c.pos(), |
| "unresolved disjunction %s (type %s)", c.Str(d), d.Kind()) |
| return nil, false |
| } |
| return c.getDefault(d.Values[0]) |
| } |
| |
| // Evaluate evaluates an expression within the given environment and indicates |
| // whether the result is complete. It will always return a non-nil result. |
| func (c *OpContext) Evaluate(env *Environment, x Expr) (result Value, complete bool) { |
| s := c.PushState(env, x.Source()) |
| |
| val := c.evalState(x, Partial) |
| |
| complete = true |
| |
| if err, _ := val.(*Bottom); err != nil && err.IsIncomplete() { |
| complete = false |
| } |
| if val == nil { |
| complete = false |
| // TODO ENSURE THIS DOESN"T HAPPEN> |
| val = &Bottom{ |
| Code: IncompleteError, |
| Err: c.Newf("UNANTICIPATED ERROR"), |
| } |
| |
| } |
| |
| _ = c.PopState(s) |
| |
| if !complete || val == nil { |
| return val, false |
| } |
| |
| return val, true |
| } |
| |
| func (c *OpContext) evaluateRec(env *Environment, x Expr, state VertexStatus) Value { |
| s := c.PushState(env, x.Source()) |
| |
| val := c.evalState(x, state) |
| if val == nil { |
| // Be defensive: this never happens, but just in case. |
| Assertf(false, "nil return value: unspecified error") |
| val = &Bottom{ |
| Code: IncompleteError, |
| Err: c.Newf("UNANTICIPATED ERROR"), |
| } |
| } |
| _ = c.PopState(s) |
| |
| return val |
| } |
| |
| // value evaluates expression v within the current environment. The result may |
| // be nil if the result is incomplete. value leaves errors untouched to that |
| // they can be collected by the caller. |
| func (c *OpContext) value(x Expr) (result Value) { |
| v := c.evalState(x, Partial) |
| |
| v, _ = c.getDefault(v) |
| v = Unwrap(v) |
| return v |
| } |
| |
| func (c *OpContext) evalState(v Expr, state VertexStatus) (result Value) { |
| savedSrc := c.src |
| c.src = v.Source() |
| err := c.errs |
| c.errs = nil |
| |
| defer func() { |
| c.errs = CombineErrors(c.src, c.errs, err) |
| |
| if v, ok := result.(*Vertex); ok { |
| if b, _ := v.BaseValue.(*Bottom); b != nil { |
| switch b.Code { |
| case IncompleteError: |
| case CycleError: |
| if state == Partial { |
| break |
| } |
| fallthrough |
| default: |
| result = b |
| } |
| } |
| } |
| |
| // TODO: remove this when we handle errors more principally. |
| if b, ok := result.(*Bottom); ok { |
| if c.src != nil && |
| b.Code == CycleError && |
| b.Err.Position() == token.NoPos && |
| len(b.Err.InputPositions()) == 0 { |
| bb := *b |
| bb.Err = errors.Wrapf(b.Err, c.src.Pos(), "") |
| result = &bb |
| } |
| c.errs = CombineErrors(c.src, c.errs, result) |
| } |
| if c.errs != nil { |
| result = c.errs |
| } |
| c.src = savedSrc |
| }() |
| |
| switch x := v.(type) { |
| case Value: |
| return x |
| |
| case Evaluator: |
| v := x.evaluate(c) |
| return v |
| |
| case Resolver: |
| arc := x.resolve(c, state) |
| if c.HasErr() { |
| return nil |
| } |
| if arc == nil { |
| return nil |
| } |
| |
| v := c.evaluate(arc, state) |
| return v |
| |
| default: |
| // This can only happen, really, if v == nil, which is not allowed. |
| panic(fmt.Sprintf("unexpected Expr type %T", v)) |
| } |
| } |
| |
| // unifyNode returns a possibly partially evaluated node value. |
| // |
| // TODO: maybe return *Vertex, *Bottom |
| // |
| func (c *OpContext) unifyNode(v Expr, state VertexStatus) (result Value) { |
| savedSrc := c.src |
| c.src = v.Source() |
| err := c.errs |
| c.errs = nil |
| |
| defer func() { |
| c.errs = CombineErrors(c.src, c.errs, err) |
| |
| if v, ok := result.(*Vertex); ok { |
| if b, _ := v.BaseValue.(*Bottom); b != nil { |
| switch b.Code { |
| case IncompleteError: |
| case CycleError: |
| if state == Partial { |
| break |
| } |
| fallthrough |
| default: |
| result = b |
| } |
| } |
| } |
| |
| // TODO: remove this when we handle errors more principally. |
| if b, ok := result.(*Bottom); ok { |
| if c.src != nil && |
| b.Code == CycleError && |
| b.Err.Position() == token.NoPos && |
| len(b.Err.InputPositions()) == 0 { |
| bb := *b |
| bb.Err = errors.Wrapf(b.Err, c.src.Pos(), "") |
| result = &bb |
| } |
| c.errs = CombineErrors(c.src, c.errs, result) |
| } |
| if c.errs != nil { |
| result = c.errs |
| } |
| c.src = savedSrc |
| }() |
| |
| switch x := v.(type) { |
| case Value: |
| return x |
| |
| case Evaluator: |
| v := x.evaluate(c) |
| return v |
| |
| case Resolver: |
| v := x.resolve(c, state) |
| if c.HasErr() { |
| return nil |
| } |
| if v == nil { |
| return nil |
| } |
| |
| if v.isUndefined() { |
| // Use node itself to allow for cycle detection. |
| c.Unify(v, AllArcs) |
| } |
| |
| return v |
| |
| default: |
| // This can only happen, really, if v == nil, which is not allowed. |
| panic(fmt.Sprintf("unexpected Expr type %T", v)) |
| } |
| } |
| |
| func (c *OpContext) lookup(x *Vertex, pos token.Pos, l Feature, state VertexStatus) *Vertex { |
| if l == InvalidLabel || x == nil { |
| // TODO: is it possible to have an invalid label here? Maybe through the |
| // API? |
| return &Vertex{} |
| } |
| |
| // var kind Kind |
| // if x.BaseValue != nil { |
| // kind = x.BaseValue.Kind() |
| // } |
| |
| switch x.BaseValue.(type) { |
| case *StructMarker: |
| if l.Typ() == IntLabel { |
| c.addErrf(0, pos, "invalid struct selector %s (type int)", l) |
| return nil |
| } |
| |
| case *ListMarker: |
| switch { |
| case l.Typ() == IntLabel: |
| switch { |
| case l.Index() < 0: |
| c.addErrf(0, pos, "invalid list index %s (index must be non-negative)", l) |
| return nil |
| case l.Index() > len(x.Arcs): |
| c.addErrf(0, pos, "invalid list index %s (out of bounds)", l) |
| return nil |
| } |
| |
| case l.IsDef(), l.IsHidden(): |
| |
| default: |
| c.addErrf(0, pos, "invalid list index %s (type string)", l) |
| return nil |
| } |
| |
| case nil: |
| // c.addErrf(IncompleteError, pos, "incomplete value %s", c.Str(x)) |
| // return nil |
| |
| case *Bottom: |
| |
| default: |
| kind := x.BaseValue.Kind() |
| if kind&(ListKind|StructKind) != 0 { |
| // c.addErrf(IncompleteError, pos, |
| // "cannot look up %s in incomplete type %s (type %s)", |
| // l, x.Source(), kind) |
| // return nil |
| } else if !l.IsDef() && !l.IsHidden() { |
| c.addErrf(0, pos, |
| "invalid selector %s for value of type %s", l, kind) |
| return nil |
| } |
| } |
| |
| a := x.Lookup(l) |
| |
| var hasCycle bool |
| outer: |
| switch { |
| case c.nonMonotonicLookupNest == 0 && c.nonMonotonicRejectNest == 0: |
| case a != nil: |
| if state == Partial { |
| a.nonMonotonicLookupGen = c.nonMonotonicGeneration |
| } |
| |
| case x.state != nil && state == Partial: |
| for _, e := range x.state.exprs { |
| if isCyclePlaceholder(e.err) { |
| hasCycle = true |
| } |
| } |
| for _, a := range x.state.usedArcs { |
| if a.Label == l { |
| a.nonMonotonicLookupGen = c.nonMonotonicGeneration |
| if c.nonMonotonicRejectNest > 0 { |
| a.nonMonotonicReject = true |
| } |
| break outer |
| } |
| } |
| a := &Vertex{Label: l, nonMonotonicLookupGen: c.nonMonotonicGeneration} |
| if c.nonMonotonicRejectNest > 0 { |
| a.nonMonotonicReject = true |
| } |
| x.state.usedArcs = append(x.state.usedArcs, a) |
| } |
| if a == nil { |
| if x.state != nil { |
| for _, e := range x.state.exprs { |
| if isCyclePlaceholder(e.err) { |
| hasCycle = true |
| } |
| } |
| } |
| code := IncompleteError |
| if !x.Accept(c, l) { |
| code = 0 |
| } else if hasCycle { |
| code = CycleError |
| } |
| // TODO: if the struct was a literal struct, we can also treat it as |
| // closed and make this a permanent error. |
| label := l.SelectorString(c.Runtime) |
| |
| // TODO(errors): add path reference and make message |
| // "undefined field %s in %s" |
| if l.IsInt() { |
| c.addErrf(code, pos, "index out of range [%d] with length %d", |
| l.Index(), len(x.Elems())) |
| } else { |
| if code != 0 && x.IsOptional(l) { |
| c.addErrf(code, pos, |
| "cannot reference optional field %s", label) |
| } else { |
| c.addErrf(code, pos, "undefined field %s", label) |
| } |
| } |
| } |
| return a |
| } |
| |
| func (c *OpContext) Label(src Expr, x Value) Feature { |
| return labelFromValue(c, src, x) |
| } |
| |
| func (c *OpContext) typeError(v Value, k Kind) { |
| if isError(v) { |
| return |
| } |
| if !IsConcrete(v) && v.Kind()&k != 0 { |
| c.addErrf(IncompleteError, pos(v), |
| "incomplete %s value '%s'", k, c.Str(v)) |
| } else { |
| c.AddErrf("cannot use %s (type %s) as type %s", c.Str(v), v.Kind(), k) |
| } |
| } |
| |
| func (c *OpContext) typeErrorAs(v Value, k Kind, as interface{}) { |
| if as == nil { |
| c.typeError(v, k) |
| return |
| } |
| if isError(v) { |
| return |
| } |
| if !IsConcrete(v) && v.Kind()&k != 0 { |
| c.addErrf(IncompleteError, pos(v), |
| "incomplete %s value '%s' in as", k, c.Str(v), as) |
| } else { |
| c.AddErrf("cannot use %s (type %s) as type %s in %v", |
| c.Str(v), v.Kind(), k, as) |
| } |
| } |
| |
| var emptyNode = &Vertex{} |
| |
| func pos(x Node) token.Pos { |
| if x.Source() == nil { |
| return token.NoPos |
| } |
| return x.Source().Pos() |
| } |
| |
| func (c *OpContext) node(orig Node, x Expr, scalar bool, state VertexStatus) *Vertex { |
| // TODO: always get the vertex. This allows a whole bunch of trickery |
| // down the line. |
| v := c.unifyNode(x, state) |
| |
| v, ok := c.getDefault(v) |
| if !ok { |
| // Error already generated by getDefault. |
| return emptyNode |
| } |
| |
| // The two if blocks below are rather subtle. If we have an error of |
| // the sentinel value cycle, we have earlier determined that the cycle is |
| // allowed and that it can be ignored here. Any other CycleError is an |
| // annotated cycle error that could be taken as is. |
| // TODO: do something simpler. |
| if scalar { |
| if w := Unwrap(v); !isCyclePlaceholder(w) { |
| v = w |
| } |
| } |
| |
| node, ok := v.(*Vertex) |
| if ok && !isCyclePlaceholder(node.BaseValue) { |
| v = node.Value() |
| } |
| |
| switch nv := v.(type) { |
| case nil: |
| switch orig.(type) { |
| case *ForClause: |
| c.addErrf(IncompleteError, pos(x), |
| "cannot range over %s (incomplete)", |
| c.Str(x)) |
| default: |
| c.addErrf(IncompleteError, pos(x), |
| "%s undefined (%s is incomplete)", c.Str(orig), c.Str(x)) |
| } |
| return emptyNode |
| |
| case *Bottom: |
| // TODO: this is a bit messy. In some cases errors are already added |
| // and in some cases not. Not a huge deal, as errors will be uniqued |
| // down the line, but could be better. |
| c.AddBottom(nv) |
| return emptyNode |
| |
| case *Vertex: |
| if node == nil { |
| panic("unexpected markers with nil node") |
| } |
| |
| default: |
| if kind := v.Kind(); kind&StructKind != 0 { |
| switch orig.(type) { |
| case *ForClause: |
| c.addErrf(IncompleteError, pos(x), |
| "cannot range over %s (incomplete type %s)", |
| c.Str(x), kind) |
| default: |
| c.addErrf(IncompleteError, pos(x), |
| "%s undefined as %s is incomplete (type %s)", |
| c.Str(orig), c.Str(x), kind) |
| } |
| return emptyNode |
| |
| } else if !ok { |
| c.addErrf(0, pos(x), // TODO(error): better message. |
| "invalid operand %s (found %s, want list or struct)", |
| x.Source(), v.Kind()) |
| return emptyNode |
| } |
| } |
| |
| return node |
| } |
| |
| // Elems returns the elements of a list. |
| func (c *OpContext) Elems(v Value) []*Vertex { |
| list := c.list(v) |
| return list.Elems() |
| } |
| |
| func (c *OpContext) list(v Value) *Vertex { |
| x, ok := v.(*Vertex) |
| if !ok || !x.IsList() { |
| c.typeError(v, ListKind) |
| return emptyNode |
| } |
| return x |
| } |
| |
| func (c *OpContext) scalar(v Value) Value { |
| v = Unwrap(v) |
| switch v.(type) { |
| case *Null, *Bool, *Num, *String, *Bytes: |
| default: |
| c.typeError(v, ScalarKinds) |
| } |
| return v |
| } |
| |
| var zero = &Num{K: NumKind} |
| |
| func (c *OpContext) Num(v Value, as interface{}) *Num { |
| v = Unwrap(v) |
| if isError(v) { |
| return zero |
| } |
| x, ok := v.(*Num) |
| if !ok { |
| c.typeErrorAs(v, NumKind, as) |
| return zero |
| } |
| return x |
| } |
| |
| func (c *OpContext) Int64(v Value) int64 { |
| v = Unwrap(v) |
| if isError(v) { |
| return 0 |
| } |
| x, ok := v.(*Num) |
| if !ok { |
| c.typeError(v, IntKind) |
| return 0 |
| } |
| i, err := x.X.Int64() |
| if err != nil { |
| c.AddErrf("number is not an int64: %v", err) |
| return 0 |
| } |
| return i |
| } |
| |
| func (c *OpContext) uint64(v Value, as string) uint64 { |
| v = Unwrap(v) |
| if isError(v) { |
| return 0 |
| } |
| x, ok := v.(*Num) |
| if !ok { |
| c.typeErrorAs(v, IntKind, as) |
| return 0 |
| } |
| if x.X.Negative { |
| // TODO: improve message |
| c.AddErrf("cannot convert negative number to uint64") |
| return 0 |
| } |
| if !x.X.Coeff.IsUint64() { |
| // TODO: improve message |
| c.AddErrf("cannot convert number %s to uint64", x.X) |
| return 0 |
| } |
| return x.X.Coeff.Uint64() |
| } |
| |
| func (c *OpContext) BoolValue(v Value) bool { |
| return c.boolValue(v, nil) |
| } |
| |
| func (c *OpContext) boolValue(v Value, as interface{}) bool { |
| v = Unwrap(v) |
| if isError(v) { |
| return false |
| } |
| x, ok := v.(*Bool) |
| if !ok { |
| c.typeErrorAs(v, BoolKind, as) |
| return false |
| } |
| return x.B |
| } |
| |
| func (c *OpContext) StringValue(v Value) string { |
| return c.stringValue(v, nil) |
| } |
| |
| // ToBytes returns the bytes value of a scalar value. |
| func (c *OpContext) ToBytes(v Value) []byte { |
| if x, ok := v.(*Bytes); ok { |
| return x.B |
| } |
| return []byte(c.ToString(v)) |
| } |
| |
| // ToString returns the string value of a scalar value. |
| func (c *OpContext) ToString(v Value) string { |
| return c.toStringValue(v, StringKind|NumKind|BytesKind|BoolKind, nil) |
| |
| } |
| |
| func (c *OpContext) stringValue(v Value, as interface{}) string { |
| return c.toStringValue(v, StringKind, as) |
| } |
| |
| func (c *OpContext) toStringValue(v Value, k Kind, as interface{}) string { |
| v = Unwrap(v) |
| if isError(v) { |
| return "" |
| } |
| if v.Kind()&k == 0 { |
| if as == nil { |
| c.typeError(v, k) |
| } else { |
| c.typeErrorAs(v, k, as) |
| } |
| return "" |
| } |
| switch x := v.(type) { |
| case *String: |
| return x.Str |
| |
| case *Bytes: |
| return bytesToString(x.B) |
| |
| case *Num: |
| return x.X.String() |
| |
| case *Bool: |
| if x.B { |
| return "true" |
| } |
| return "false" |
| |
| default: |
| c.addErrf(IncompleteError, c.pos(), |
| "non-concrete value %s (type %s)", c.Str(v), v.Kind()) |
| } |
| return "" |
| } |
| |
| func bytesToString(b []byte) string { |
| b, _ = unicode.UTF8.NewDecoder().Bytes(b) |
| return string(b) |
| } |
| |
| func (c *OpContext) bytesValue(v Value, as interface{}) []byte { |
| v = Unwrap(v) |
| if isError(v) { |
| return nil |
| } |
| x, ok := v.(*Bytes) |
| if !ok { |
| c.typeErrorAs(v, BytesKind, as) |
| return nil |
| } |
| return x.B |
| } |
| |
| var matchNone = regexp.MustCompile("^$") |
| |
| func (c *OpContext) regexp(v Value) *regexp.Regexp { |
| v = Unwrap(v) |
| if isError(v) { |
| return matchNone |
| } |
| switch x := v.(type) { |
| case *String: |
| if x.RE != nil { |
| return x.RE |
| } |
| // TODO: synchronization |
| p, err := regexp.Compile(x.Str) |
| if err != nil { |
| // FatalError? How to cache error |
| c.AddErrf("invalid regexp: %s", err) |
| x.RE = matchNone |
| } else { |
| x.RE = p |
| } |
| return x.RE |
| |
| case *Bytes: |
| if x.RE != nil { |
| return x.RE |
| } |
| // TODO: synchronization |
| p, err := regexp.Compile(string(x.B)) |
| if err != nil { |
| c.AddErrf("invalid regexp: %s", err) |
| x.RE = matchNone |
| } else { |
| x.RE = p |
| } |
| return x.RE |
| |
| default: |
| c.typeError(v, StringKind|BytesKind) |
| return matchNone |
| } |
| } |
| |
| // newNum creates a new number of the given kind. It reports an error value |
| // instead if any error occurred. |
| func (c *OpContext) newNum(d *apd.Decimal, k Kind, sources ...Node) Value { |
| if c.HasErr() { |
| return c.Err() |
| } |
| return &Num{Src: c.src, X: *d, K: k} |
| } |
| |
| func (c *OpContext) NewInt64(n int64, sources ...Node) Value { |
| if c.HasErr() { |
| return c.Err() |
| } |
| d := apd.New(n, 0) |
| return &Num{Src: c.src, X: *d, K: IntKind} |
| } |
| |
| func (c *OpContext) NewString(s string) Value { |
| if c.HasErr() { |
| return c.Err() |
| } |
| return &String{Src: c.src, Str: s} |
| } |
| |
| func (c *OpContext) newBytes(b []byte) Value { |
| if c.HasErr() { |
| return c.Err() |
| } |
| return &Bytes{Src: c.src, B: b} |
| } |
| |
| func (c *OpContext) newBool(b bool) Value { |
| if c.HasErr() { |
| return c.Err() |
| } |
| return &Bool{Src: c.src, B: b} |
| } |
| |
| func (c *OpContext) newList(src ast.Node, parent *Vertex) *Vertex { |
| return &Vertex{Parent: parent, BaseValue: &ListMarker{}} |
| } |
| |
| // Str reports a debug string of x. |
| func (c *OpContext) Str(x Node) string { |
| if c.Format == nil { |
| return fmt.Sprintf("%T", x) |
| } |
| return c.Format(x) |
| } |
| |
| // NewList returns a new list for the given values. |
| func (c *OpContext) NewList(values ...Value) *Vertex { |
| // TODO: consider making this a literal list instead. |
| list := &ListLit{} |
| v := &Vertex{ |
| Conjuncts: []Conjunct{{Env: nil, x: list}}, |
| } |
| |
| for _, x := range values { |
| list.Elems = append(list.Elems, x) |
| } |
| c.Unify(v, Finalized) |
| return v |
| } |