blob: d738455c6a7195639ebf6ea832999171ab65e52e [file] [log] [blame]
// 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/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) {
// No need to take position of expression.
c.AddErr(c.NewPosf(token.NoPos,
"invalid operand %s ('%s' requires concrete value)", 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{}) {
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) {
w, complete := c.Evaluate(env, x)
w, ok := c.getDefault(w)
if !ok {
return w, false
}
v := Unwrap(w)
if !IsConcrete(v) {
complete = false
b := c.NewErrf("non-concrete value %v in operand to %s", w, 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)", 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 &&
len(errors.Positions(b.Err)) == 0 {
bb := *b
bb.Err = errors.Wrapf(b.Err, c.src.Pos(), "")
result = &bb
}
if c.errs != result {
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", 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: %s", k, v)
} else {
c.AddErrf("cannot use %s (type %s) as type %s", 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 in %v: %s", k, as, v)
} else {
c.AddErrf("cannot use %s (type %s) as type %s in %v", 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)", x)
default:
c.addErrf(IncompleteError, pos(x),
"%s undefined (%s is incomplete)", orig, 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)", x, kind)
default:
c.addErrf(IncompleteError, pos(x),
"%s undefined as %s is incomplete (type %s)", orig, 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)", 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
}