blob: 67933fa5be8643a8303bcedfe0060a691b73d4fa [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 (
"bytes"
"fmt"
"io"
"regexp"
"github.com/cockroachdb/apd/v2"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/errors"
"cuelang.org/go/cue/token"
)
// A StructLit represents an unevaluated struct literal or file body.
type StructLit struct {
Src ast.Node // ast.File or ast.StructLit
Decls []Decl
// TODO: record the merge order somewhere.
// The below fields are redundant to Decls and are computed with Init.
// field marks the optional conjuncts of all explicit Fields.
// Required Fields are marked as empty
Fields []FieldInfo
Dynamic []*DynamicField
// excluded are all literal fields that already exist.
Bulk []*BulkOptionalField
Additional []Expr
HasEmbed bool
IsOpen bool // has a ...
initialized bool
types OptionalType
// administrative fields like hasreferences.
// hasReferences bool
}
func (o *StructLit) IsFile() bool {
_, ok := o.Src.(*ast.File)
return ok
}
type FieldInfo struct {
Label Feature
Optional []Node
}
func (x *StructLit) HasOptional() bool {
return x.types&(HasField|HasPattern|HasAdditional) != 0
}
func (x *StructLit) Source() ast.Node { return x.Src }
func (x *StructLit) evaluate(c *OpContext) Value {
e := c.Env(0)
v := &Vertex{Conjuncts: []Conjunct{{e, x, CloseInfo{}}}}
// evaluate may not finalize a field, as the resulting value may be
// used in a context where more conjuncts are added. It may also lead
// to disjuncts being in a partially expanded state, leading to
// misaligned nodeContexts.
c.Unify(v, AllArcs)
return v
}
// TODO: remove this method
func (o *StructLit) MarkField(f Feature) {
o.Fields = append(o.Fields, FieldInfo{Label: f})
}
func (o *StructLit) Init() {
if o.initialized {
return
}
o.initialized = true
for _, d := range o.Decls {
switch x := d.(type) {
case *Field:
if o.fieldIndex(x.Label) < 0 {
o.Fields = append(o.Fields, FieldInfo{Label: x.Label})
}
case *OptionalField:
p := o.fieldIndex(x.Label)
if p < 0 {
p = len(o.Fields)
o.Fields = append(o.Fields, FieldInfo{Label: x.Label})
}
o.Fields[p].Optional = append(o.Fields[p].Optional, x)
o.types |= HasField
case *DynamicField:
o.Dynamic = append(o.Dynamic, x)
o.types |= HasDynamic
case Expr:
o.HasEmbed = true
case *ForClause, Yielder:
o.HasEmbed = true
case *BulkOptionalField:
o.Bulk = append(o.Bulk, x)
o.types |= HasPattern
switch x.Filter.(type) {
case *BasicType, *Top:
default:
o.types |= HasComplexPattern
}
case *Ellipsis:
expr := x.Value
if x.Value == nil {
o.IsOpen = true
o.types |= IsOpen
// TODO(perf): encode more efficiently.
expr = &Top{}
} else {
o.types |= HasAdditional
}
o.Additional = append(o.Additional, expr)
default:
panic("unreachable")
}
}
}
func (o *StructLit) fieldIndex(f Feature) int {
for i := range o.Fields {
if o.Fields[i].Label == f {
return i
}
}
return -1
}
func (o *StructLit) OptionalTypes() OptionalType {
return o.types
}
func (o *StructLit) IsOptional(label Feature) bool {
for _, f := range o.Fields {
if f.Label == label && len(f.Optional) > 0 {
return true
}
}
return false
}
// FIELDS
//
// Fields can also be used as expressions whereby the value field is the
// expression this allows retaining more context.
// Field represents a field with a fixed label. It can be a regular field,
// definition or hidden field.
//
// foo: bar
// #foo: bar
// _foo: bar
//
// Legacy:
//
// Foo :: bar
//
type Field struct {
Src *ast.Field
Label Feature
Value Expr
}
func (x *Field) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
// An OptionalField represents an optional regular field.
//
// foo?: expr
//
type OptionalField struct {
Src *ast.Field
Label Feature
Value Expr
}
func (x *OptionalField) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
// A BulkOptionalField represents a set of optional field.
//
// [expr]: expr
//
type BulkOptionalField struct {
Src *ast.Field // Elipsis or Field
Filter Expr
Value Expr
Label Feature // for reference and formatting
}
func (x *BulkOptionalField) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
// A Ellipsis represents a set of optional fields of a given type.
//
// ...T
//
type Ellipsis struct {
Src *ast.Ellipsis
Value Expr
}
func (x *Ellipsis) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
// A DynamicField represents a regular field for which the key is computed.
//
// "\(expr)": expr
// (expr): expr
//
type DynamicField struct {
Src *ast.Field
Key Expr
Value Expr
}
func (x *DynamicField) IsOptional() bool {
return x.Src.Optional != token.NoPos
}
func (x *DynamicField) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
// A ListLit represents an unevaluated list literal.
//
// [a, for x in src { ... }, b, ...T]
//
type ListLit struct {
Src *ast.ListLit
// scalars, comprehensions, ...T
Elems []Elem
}
func (x *ListLit) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *ListLit) evaluate(c *OpContext) Value {
e := c.Env(0)
v := &Vertex{Conjuncts: []Conjunct{{e, x, CloseInfo{}}}}
// TODO: should be AllArcs and then use Finalize for builtins?
c.Unify(v, Finalized) // TODO: also partial okay?
return v
}
// Null represents null. It can be used as a Value and Expr.
type Null struct {
Src ast.Node
}
func (x *Null) Source() ast.Node { return x.Src }
func (x *Null) Kind() Kind { return NullKind }
// Bool is a boolean value. It can be used as a Value and Expr.
type Bool struct {
Src ast.Node
B bool
}
func (x *Bool) Source() ast.Node { return x.Src }
func (x *Bool) Kind() Kind { return BoolKind }
// Num is a numeric value. It can be used as a Value and Expr.
type Num struct {
Src ast.Node
K Kind // needed?
X apd.Decimal // Is integer if the apd.Decimal is an integer.
}
// TODO: do we need this?
// func NewNumFromString(src ast.Node, s string) Value {
// n := &Num{Src: src, K: IntKind}
// if strings.ContainsAny(s, "eE.") {
// n.K = FloatKind
// }
// _, _, err := n.X.SetString(s)
// if err != nil {
// pos := token.NoPos
// if src != nil {
// pos = src.Pos()
// }
// return &Bottom{Err: errors.Newf(pos, "invalid number: %v", err)}
// }
// return n
// }
func (x *Num) Source() ast.Node { return x.Src }
func (x *Num) Kind() Kind { return x.K }
// TODO: do we still need this?
// func (x *Num) Specialize(k Kind) Value {
// k = k & x.K
// if k == x.K {
// return x
// }
// y := *x
// y.K = k
// return &y
// }
// String is a string value. It can be used as a Value and Expr.
type String struct {
Src ast.Node
Str string
RE *regexp.Regexp // only set if needed
}
func (x *String) Source() ast.Node { return x.Src }
func (x *String) Kind() Kind { return StringKind }
// Bytes is a bytes value. It can be used as a Value and Expr.
type Bytes struct {
Src ast.Node
B []byte
RE *regexp.Regexp // only set if needed
}
func (x *Bytes) Source() ast.Node { return x.Src }
func (x *Bytes) Kind() Kind { return BytesKind }
// Composites: the evaluated fields of a composite are recorded in the arc
// vertices.
type ListMarker struct {
Src ast.Node
IsOpen bool
}
func (x *ListMarker) Source() ast.Node { return x.Src }
func (x *ListMarker) Kind() Kind { return ListKind }
func (x *ListMarker) node() {}
type StructMarker struct {
// NeedClose is used to signal that the evaluator should close this struct.
// It is only set by the close builtin.
NeedClose bool
}
func (x *StructMarker) Source() ast.Node { return nil }
func (x *StructMarker) Kind() Kind { return StructKind }
func (x *StructMarker) node() {}
// Top represents all possible values. It can be used as a Value and Expr.
type Top struct{ Src *ast.Ident }
func (x *Top) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *Top) Kind() Kind { return TopKind }
// BasicType represents all values of a certain Kind. It can be used as a Value
// and Expr.
//
// string
// int
// num
// bool
//
type BasicType struct {
Src *ast.Ident
K Kind
}
func (x *BasicType) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *BasicType) Kind() Kind { return x.K }
// TODO: do we still need this?
// func (x *BasicType) Specialize(k Kind) Value {
// k = x.K & k
// if k == x.K {
// return x
// }
// y := *x
// y.K = k
// return &y
// }
// TODO: should we use UnaryExpr for Bound now we have BoundValue?
// BoundExpr represents an unresolved unary comparator.
//
// <a
// =~MyPattern
//
type BoundExpr struct {
Src *ast.UnaryExpr
Op Op
Expr Expr
}
func (x *BoundExpr) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *BoundExpr) evaluate(ctx *OpContext) Value {
v := ctx.value(x.Expr)
if isError(v) {
return v
}
switch k := v.Kind(); k {
case IntKind, FloatKind, NumKind, StringKind, BytesKind:
case NullKind:
if x.Op != NotEqualOp {
err := ctx.NewPosf(pos(x.Expr),
"cannot use null for bound %s", x.Op)
return &Bottom{Err: err}
}
default:
mask := IntKind | FloatKind | NumKind | StringKind | BytesKind
if x.Op == NotEqualOp {
mask |= NullKind
}
if k&mask != 0 {
ctx.addErrf(IncompleteError, token.NoPos, // TODO(errors): use ctx.pos()?
"non-concrete value %s for bound %s", x.Expr, x.Op)
return nil
}
err := ctx.NewPosf(pos(x.Expr),
"invalid value %s (type %s) for bound %s", v, k, x.Op)
return &Bottom{Err: err}
}
if v, ok := x.Expr.(Value); ok {
if v == nil || v.Concreteness() > Concrete {
return ctx.NewErrf("bound has fixed non-concrete value")
}
return &BoundValue{x.Src, x.Op, v}
}
// This simplifies boundary expressions. It is an alternative to an
// evaluation strategy that makes nodes increasingly more specific.
//
// For instance, a completely different implementation would be to allow
// the precense of a concrete value to ignore incomplete errors.
//
// TODO: consider an alternative approach.
switch y := v.(type) {
case *BoundValue:
switch {
case y.Op == NotEqualOp:
switch x.Op {
case LessEqualOp, LessThanOp, GreaterEqualOp, GreaterThanOp:
// <(!=3) => number
// Smaller than an arbitrarily large number is any number.
return &BasicType{K: y.Kind()}
case NotEqualOp:
// !=(!=3) ==> 3
// Not a value that is anything but a given value is that
// given value.
return y.Value
}
case x.Op == NotEqualOp:
// Invert if applicable.
switch y.Op {
case LessEqualOp:
return &BoundValue{x.Src, GreaterThanOp, y.Value}
case LessThanOp:
return &BoundValue{x.Src, GreaterEqualOp, y.Value}
case GreaterEqualOp:
return &BoundValue{x.Src, LessThanOp, y.Value}
case GreaterThanOp:
return &BoundValue{x.Src, LessEqualOp, y.Value}
}
case (x.Op == LessThanOp || x.Op == LessEqualOp) &&
(y.Op == GreaterThanOp || y.Op == GreaterEqualOp),
(x.Op == GreaterThanOp || x.Op == GreaterEqualOp) &&
(y.Op == LessThanOp || y.Op == LessEqualOp):
// <(>=3)
// Something smaller than an arbitrarily large number is any number.
return &BasicType{K: y.Kind()}
case x.Op == LessThanOp &&
(y.Op == LessEqualOp || y.Op == LessThanOp),
x.Op == GreaterThanOp &&
(y.Op == GreaterEqualOp || y.Op == GreaterThanOp):
// <(<=x) => <x
// <(<x) => <x
// Less than something that is less or equal to x is less than x.
return &BoundValue{x.Src, x.Op, y.Value}
case x.Op == LessEqualOp &&
(y.Op == LessEqualOp || y.Op == LessThanOp),
x.Op == GreaterEqualOp &&
(y.Op == GreaterEqualOp || y.Op == GreaterThanOp):
// <=(<x) => <x
// <=(<=x) => <=x
// Less or equal than something that is less than x is less than x.
return y
}
case *BasicType:
switch x.Op {
case LessEqualOp, LessThanOp, GreaterEqualOp, GreaterThanOp:
return y
}
}
if v.Concreteness() > Concrete {
// TODO(errors): analyze dependencies of x.Expr to get positions.
ctx.addErrf(IncompleteError, token.NoPos, // TODO(errors): use ctx.pos()?
"non-concrete value %s for bound %s", x.Expr, x.Op)
return nil
}
return &BoundValue{x.Src, x.Op, v}
}
// A BoundValue is a fully evaluated unary comparator that can be used to
// validate other values.
//
// <5
// =~"Name$"
//
type BoundValue struct {
Src ast.Expr
Op Op
Value Value
}
func (x *BoundValue) Source() ast.Node { return x.Src }
func (x *BoundValue) Kind() Kind {
k := x.Value.Kind()
switch k {
case IntKind, FloatKind, NumKind:
return NumKind
case NullKind:
if x.Op == NotEqualOp {
return TopKind &^ NullKind
}
}
return k
}
func (x *BoundValue) validate(c *OpContext, y Value) *Bottom {
a := y // Can be list or struct.
b := c.scalar(x.Value)
if c.HasErr() {
return c.Err()
}
switch v := BinOp(c, x.Op, a, b).(type) {
case *Bottom:
return v
case *Bool:
if v.B {
return nil
}
// TODO(errors): use "invalid value %v (not an %s)" if x is a
// predeclared identifier such as `int`.
err := c.Newf("invalid value %v (out of bound %s)", y, x)
err.AddPosition(y)
return &Bottom{Src: c.src, Err: err, Code: EvalError}
default:
panic(fmt.Sprintf("unsupported type %T", v))
}
}
func (x *BoundValue) validateStr(c *OpContext, a string) bool {
if str, ok := x.Value.(*String); ok {
b := str.Str
switch x.Op {
case LessEqualOp:
return a <= b
case LessThanOp:
return a < b
case GreaterEqualOp:
return a >= b
case GreaterThanOp:
return a > b
case EqualOp:
return a == b
case NotEqualOp:
return a != b
case MatchOp:
return c.regexp(x.Value).MatchString(a)
case NotMatchOp:
return !c.regexp(x.Value).MatchString(a)
}
}
return x.validate(c, &String{Str: a}) == nil
}
func (x *BoundValue) validateInt(c *OpContext, a int64) bool {
switch n := x.Value.(type) {
case *Num:
b, err := n.X.Int64()
if err != nil {
break
}
switch x.Op {
case LessEqualOp:
return a <= b
case LessThanOp:
return a < b
case GreaterEqualOp:
return a >= b
case GreaterThanOp:
return a > b
case EqualOp:
return a == b
case NotEqualOp:
return a != b
}
}
return x.validate(c, c.NewInt64(a)) == nil
}
// A NodeLink is used during computation to refer to an existing Vertex.
// It is used to signal a potential cycle or reference.
// Note that a NodeLink may be used as a value. This should be taken into
// account.
type NodeLink struct {
Node *Vertex
}
func (x *NodeLink) Kind() Kind {
return x.Node.Kind()
}
func (x *NodeLink) Source() ast.Node { return x.Node.Source() }
func (x *NodeLink) resolve(c *OpContext, state VertexStatus) *Vertex {
return x.Node
}
// A FieldReference represents a lexical reference to a field.
//
// a
//
type FieldReference struct {
Src *ast.Ident
UpCount int32
Label Feature
}
func (x *FieldReference) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *FieldReference) resolve(c *OpContext, state VertexStatus) *Vertex {
n := c.relNode(x.UpCount)
pos := pos(x)
return c.lookup(n, pos, x.Label, state)
}
// A LabelReference refers to the string or integer value of a label.
//
// [X=Pattern]: b: X
//
type LabelReference struct {
Src *ast.Ident
UpCount int32
}
// TODO: should this implement resolver at all?
func (x *LabelReference) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *LabelReference) evaluate(ctx *OpContext) Value {
label := ctx.relLabel(x.UpCount)
if label == 0 {
// There is no label. This may happen if a LabelReference is evaluated
// outside of the context of a parent node, for instance if an
// "additional" items or properties is evaluated in isolation.
//
// TODO: this should return the pattern of the label.
return &BasicType{K: StringKind}
}
return label.ToValue(ctx)
}
// A DynamicReference is like a LabelReference, but with a computed label.
//
// X=(x): X
// X="\(x)": X
//
type DynamicReference struct {
Src *ast.Ident
UpCount int32
Label Expr
// TODO: only use aliases and store the actual expression only in the scope.
// The feature is unique for every instance. This will also allow dynamic
// fields to be ordered among normal fields.
//
// This could also be used to assign labels to embedded values, if they
// don't match a label.
Alias Feature
}
func (x *DynamicReference) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *DynamicReference) resolve(ctx *OpContext, state VertexStatus) *Vertex {
e := ctx.Env(x.UpCount)
frame := ctx.PushState(e, x.Src)
v := ctx.value(x.Label)
ctx.PopState(frame)
f := ctx.Label(x.Label, v)
return ctx.lookup(e.Vertex, pos(x), f, state)
}
// An ImportReference refers to an imported package.
//
// import "strings"
//
// strings.ToLower("Upper")
//
type ImportReference struct {
Src *ast.Ident
ImportPath Feature
Label Feature // for informative purposes
}
func (x *ImportReference) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *ImportReference) resolve(ctx *OpContext, state VertexStatus) *Vertex {
path := x.ImportPath.StringValue(ctx)
v, _ := ctx.Runtime.LoadImport(path)
return v
}
// A LetReference evaluates a let expression in its original environment.
//
// let X = x
//
type LetReference struct {
Src *ast.Ident
UpCount int32
Label Feature // for informative purposes
X Expr
}
func (x *LetReference) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *LetReference) resolve(c *OpContext, state VertexStatus) *Vertex {
e := c.Env(x.UpCount)
label := e.Vertex.Label
if x.X == nil {
panic("nil expression")
}
// Anonymous arc.
return &Vertex{Parent: nil, Label: label, Conjuncts: []Conjunct{{e, x.X, CloseInfo{}}}}
}
func (x *LetReference) evaluate(c *OpContext) Value {
e := c.Env(x.UpCount)
// Not caching let expressions may lead to exponential behavior.
return e.evalCached(c, x.X)
}
// A SelectorExpr looks up a fixed field in an expression.
//
// X.Sel
//
type SelectorExpr struct {
Src *ast.SelectorExpr
X Expr
Sel Feature
}
func (x *SelectorExpr) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *SelectorExpr) resolve(c *OpContext, state VertexStatus) *Vertex {
n := c.node(x, x.X, x.Sel.IsRegular(), state)
if n == emptyNode {
return n
}
if n.status == Partial {
if b := n.state.incompleteErrors(); b != nil && b.Code < CycleError {
n.BaseValue = b
return n
}
}
return c.lookup(n, x.Src.Sel.Pos(), x.Sel, state)
}
// IndexExpr is like a selector, but selects an index.
//
// X[Index]
//
type IndexExpr struct {
Src *ast.IndexExpr
X Expr
Index Expr
}
func (x *IndexExpr) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *IndexExpr) resolve(ctx *OpContext, state VertexStatus) *Vertex {
// TODO: support byte index.
n := ctx.node(x, x.X, true, state)
i := ctx.value(x.Index)
if n == emptyNode {
return n
}
if n.status == Partial {
if b := n.state.incompleteErrors(); b != nil && b.Code < CycleError {
n.BaseValue = b
return n
}
}
f := ctx.Label(x.Index, i)
return ctx.lookup(n, x.Src.Index.Pos(), f, state)
}
// A SliceExpr represents a slice operation. (Not currently in spec.)
//
// X[Lo:Hi:Stride]
//
type SliceExpr struct {
Src *ast.SliceExpr
X Expr
Lo Expr
Hi Expr
Stride Expr
}
func (x *SliceExpr) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *SliceExpr) evaluate(c *OpContext) Value {
// TODO: strides
v := c.value(x.X)
const as = "slice index"
switch v := v.(type) {
case nil:
c.addErrf(IncompleteError, c.pos(), "non-concrete slice subject %s", x.X)
return nil
case *Vertex:
if !v.IsList() {
break
}
var (
lo = uint64(0)
hi = uint64(len(v.Arcs))
)
if x.Lo != nil {
lo = c.uint64(c.value(x.Lo), as)
}
if x.Hi != nil {
hi = c.uint64(c.value(x.Hi), as)
if hi > uint64(len(v.Arcs)) {
return c.NewErrf("index %d out of range", hi)
}
}
if lo > hi {
return c.NewErrf("invalid slice index: %d > %d", lo, hi)
}
n := c.newList(c.src, v.Parent)
for i, a := range v.Arcs[lo:hi] {
label, err := MakeLabel(a.Source(), int64(i), IntLabel)
if err != nil {
c.AddBottom(&Bottom{Src: a.Source(), Err: err})
return nil
}
arc := *a
arc.Parent = n
arc.Label = label
n.Arcs = append(n.Arcs, &arc)
}
n.status = Finalized
return n
case *Bytes:
var (
lo = uint64(0)
hi = uint64(len(v.B))
)
if x.Lo != nil {
lo = c.uint64(c.value(x.Lo), as)
}
if x.Hi != nil {
hi = c.uint64(c.value(x.Hi), as)
if hi > uint64(len(v.B)) {
return c.NewErrf("index %d out of range", hi)
}
}
if lo > hi {
return c.NewErrf("invalid slice index: %d > %d", lo, hi)
}
return c.newBytes(v.B[lo:hi])
}
if isError(v) {
return v
}
return c.NewErrf("cannot slice %v (type %s)", v, v.Kind())
}
// An Interpolation is a string interpolation.
//
// "a \(b) c"
//
type Interpolation struct {
Src *ast.Interpolation
K Kind // string or bytes
Parts []Expr // odd: strings, even sources
}
func (x *Interpolation) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *Interpolation) evaluate(c *OpContext) Value {
buf := bytes.Buffer{}
for _, e := range x.Parts {
v := c.value(e)
if x.K == BytesKind {
buf.Write(c.ToBytes(v))
} else {
buf.WriteString(c.ToString(v))
}
}
if err := c.Err(); err != nil {
err = &Bottom{
Code: err.Code,
Err: errors.Wrapf(err.Err, pos(x), "invalid interpolation"),
}
// c.AddBottom(err)
// return nil
return err
}
if x.K == BytesKind {
return &Bytes{x.Src, buf.Bytes(), nil}
}
return &String{x.Src, buf.String(), nil}
}
// UnaryExpr is a unary expression.
//
// Op X
// -X !X +X
//
type UnaryExpr struct {
Src *ast.UnaryExpr
Op Op
X Expr
}
func (x *UnaryExpr) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *UnaryExpr) evaluate(c *OpContext) Value {
if !c.concreteIsPossible(x.Op, x.X) {
return nil
}
v := c.value(x.X)
if isError(v) {
return v
}
op := x.Op
k := kind(v)
expectedKind := k
switch op {
case SubtractOp:
if v, ok := v.(*Num); ok {
f := *v
f.X.Neg(&v.X)
f.Src = x.Src
return &f
}
expectedKind = NumKind
case AddOp:
if v, ok := v.(*Num); ok {
// TODO: wrap in thunk to save position of '+'?
return v
}
expectedKind = NumKind
case NotOp:
if v, ok := v.(*Bool); ok {
return &Bool{x.Src, !v.B}
}
expectedKind = BoolKind
}
if k&expectedKind != BottomKind {
c.addErrf(IncompleteError, pos(x.X),
"operand %s of '%s' not concrete (was %s)", x.X, op, k)
return nil
}
return c.NewErrf("invalid operation %s (%s %s)", x, op, k)
}
// BinaryExpr is a binary expression.
//
// X + Y
// X & Y
//
type BinaryExpr struct {
Src *ast.BinaryExpr
Op Op
X Expr
Y Expr
}
func (x *BinaryExpr) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *BinaryExpr) evaluate(c *OpContext) Value {
env := c.Env(0)
if x.Op == AndOp {
// Anonymous Arc
v := &Vertex{Conjuncts: []Conjunct{{env, x, CloseInfo{}}}}
c.Unify(v, Finalized)
return v
}
if !c.concreteIsPossible(x.Op, x.X) || !c.concreteIsPossible(x.Op, x.Y) {
return nil
}
// TODO: allow comparing to a literal Bottom only. Find something more
// principled perhaps. One should especially take care that two values
// evaluating to Bottom don't evaluate to true. For now we check for
// Bottom here and require that one of the values be a Bottom literal.
if x.Op == EqualOp || x.Op == NotEqualOp {
if isLiteralBottom(x.X) {
return c.validate(env, x.Src, x.Y, x.Op)
}
if isLiteralBottom(x.Y) {
return c.validate(env, x.Src, x.X, x.Op)
}
}
left, _ := c.Concrete(env, x.X, x.Op)
right, _ := c.Concrete(env, x.Y, x.Op)
if err := CombineErrors(x.Src, left, right); err != nil {
return err
}
if err := c.Err(); err != nil {
return err
}
return BinOp(c, x.Op, left, right)
}
func (c *OpContext) validate(env *Environment, src ast.Node, x Expr, op Op) (r Value) {
s := c.PushState(env, src)
if c.nonMonotonicLookupNest == 0 {
c.nonMonotonicGeneration++
}
var match bool
// NOTE: using Unwrap is maybe note entirely accurate, as it may discard
// a future error. However, if it does so, the error will at least be
// reported elsewhere.
switch b := c.value(x).(type) {
case nil:
case *Bottom:
if b.Code == CycleError {
c.PopState(s)
c.AddBottom(b)
return nil
}
match = op == EqualOp
// We have a nonmonotonic use of a failure. Referenced fields should
// not be added anymore.
c.nonMonotonicRejectNest++
c.evalState(x, Partial)
c.nonMonotonicRejectNest--
default:
// TODO(cycle): if EqualOp:
// - ensure to pass special status to if clause or keep a track of "hot"
// paths.
// - evaluate hypothetical struct
// - walk over all fields and verify that fields are not contradicting
// previously marked fields.
//
switch {
case b.Concreteness() > Concrete:
// TODO: mimic comparison to bottom semantics. If it is a valid
// value, check for concreteness that this level only. This
// should ultimately be replaced with an exists and valid
// builtin.
match = op == EqualOp
default:
match = op != EqualOp
}
c.nonMonotonicLookupNest++
c.evalState(x, Partial)
c.nonMonotonicLookupNest--
}
c.PopState(s)
return &Bool{src, match}
}
// A CallExpr represents a call to a builtin.
//
// len(x)
// strings.ToLower(x)
//
type CallExpr struct {
Src *ast.CallExpr
Fun Expr
Args []Expr
}
func (x *CallExpr) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *CallExpr) evaluate(c *OpContext) Value {
fun := c.value(x.Fun)
var b *Builtin
switch f := fun.(type) {
case *Builtin:
b = f
case *BuiltinValidator:
// We allow a validator that takes no arguments accept the validated
// value to be called with zero arguments.
switch {
case f.Src != nil:
c.AddErrf("cannot call previously called validator %s", x.Fun)
case f.Builtin.IsValidator(len(x.Args)):
v := *f
v.Src = x
return &v
default:
b = f.Builtin
}
default:
c.AddErrf("cannot call non-function %s (type %s)", x.Fun, kind(fun))
return nil
}
args := []Value{}
for i, a := range x.Args {
expr := c.value(a)
switch v := expr.(type) {
case nil:
// There SHOULD be an error in the context. If not, we generate
// one.
c.Assertf(pos(x.Fun), c.HasErr(),
"argument %d to function %s is incomplete", i, x.Fun)
case *Bottom:
// TODO(errors): consider adding an argument index for this errors.
// On the other hand, this error is really not related to the
// argument itself, so maybe it is good as it is.
c.AddBottom(v)
default:
args = append(args, expr)
}
}
if c.HasErr() {
return nil
}
if b.IsValidator(len(args)) {
return &BuiltinValidator{x, b, args}
}
result := b.call(c, pos(x), args)
if result == nil {
return nil
}
return c.evalState(result, Partial)
}
// A Builtin is a value representing a native function call.
type Builtin struct {
// TODO: make these values for better type checking.
Params []Param
Result Kind
Func func(c *OpContext, args []Value) Expr
Package Feature
Name string
}
type Param struct {
Name Feature // name of the argument; mostly for documentation
Value Value // Could become Value later, using disjunctions for defaults.
}
// Kind returns the kind mask of this parameter.
func (p Param) Kind() Kind {
return p.Value.Kind()
}
// Default reports the default value for this Param or nil if there is none.
func (p Param) Default() Value {
d, ok := p.Value.(*Disjunction)
if !ok || d.NumDefaults != 1 {
return nil
}
return d.Values[0]
}
func (x *Builtin) WriteName(w io.Writer, c *OpContext) {
_, _ = fmt.Fprintf(w, "%s.%s", x.Package.StringValue(c), x.Name)
}
// Kind here represents the case where Builtin is used as a Validator.
func (x *Builtin) Kind() Kind {
return FuncKind
}
func (x *Builtin) BareValidator() *BuiltinValidator {
if len(x.Params) != 1 ||
(x.Result != BoolKind && x.Result != BottomKind) {
return nil
}
return &BuiltinValidator{Builtin: x}
}
// IsValidator reports whether b should be interpreted as a Validator for the
// given number of arguments.
func (b *Builtin) IsValidator(numArgs int) bool {
return numArgs == len(b.Params)-1 &&
b.Result&^BoolKind == 0 &&
b.Params[numArgs].Default() == nil
}
func bottom(v Value) *Bottom {
if x, ok := v.(*Vertex); ok {
v = x.Value()
}
b, _ := v.(*Bottom)
return b
}
func (x *Builtin) call(c *OpContext, p token.Pos, args []Value) Expr {
fun := x // right now always x.
if len(args) > len(x.Params) {
c.addErrf(0, p,
"too many arguments in call to %s (have %d, want %d)",
fun, len(args), len(x.Params))
return nil
}
for i := len(args); i < len(x.Params); i++ {
v := x.Params[i].Default()
if v == nil {
c.addErrf(0, p,
"not enough arguments in call to %s (have %d, want %d)",
fun, len(args), len(x.Params))
return nil
}
args = append(args, v)
}
for i, a := range args {
if x.Params[i].Kind() == BottomKind {
continue
}
if b := bottom(a); b != nil {
return b
}
if k := kind(a); x.Params[i].Kind()&k == BottomKind {
code := EvalError
b, _ := args[i].(*Bottom)
if b != nil {
code = b.Code
}
c.addErrf(code, pos(a),
"cannot use %s (type %s) as %s in argument %d to %s",
a, k, x.Params[i].Kind(), i+1, fun)
return nil
}
v := x.Params[i].Value
if _, ok := v.(*BasicType); !ok {
env := c.Env(0)
x := &BinaryExpr{Op: AndOp, X: v, Y: a}
n := &Vertex{Conjuncts: []Conjunct{{env, x, CloseInfo{}}}}
c.Unify(n, Finalized)
if _, ok := n.BaseValue.(*Bottom); ok {
c.addErrf(0, pos(a),
"cannot use %s as %s in argument %d to %s",
a, v, i+1, fun)
return nil
}
args[i] = n
}
}
return x.Func(c, args)
}
func (x *Builtin) Source() ast.Node { return nil }
// A BuiltinValidator is a Value that results from evaluation a partial call
// to a builtin (using CallExpr).
//
// strings.MinRunes(4)
//
type BuiltinValidator struct {
Src *CallExpr
Builtin *Builtin
Args []Value // any but the first value
}
func (x *BuiltinValidator) Source() ast.Node {
if x.Src == nil {
return x.Builtin.Source()
}
return x.Src.Source()
}
func (x *BuiltinValidator) Pos() token.Pos {
if src := x.Source(); src != nil {
return src.Pos()
}
return token.NoPos
}
func (x *BuiltinValidator) Kind() Kind {
return x.Builtin.Params[0].Kind()
}
func (x *BuiltinValidator) validate(c *OpContext, v Value) *Bottom {
args := make([]Value, len(x.Args)+1)
args[0] = v
copy(args[1:], x.Args)
return validateWithBuiltin(c, x.Pos(), x.Builtin, args)
}
func validateWithBuiltin(c *OpContext, src token.Pos, b *Builtin, args []Value) *Bottom {
var severeness ErrorCode
var err errors.Error
res := b.call(c, src, args)
switch v := res.(type) {
case nil:
return nil
case *Bottom:
if v == nil {
return nil // caught elsewhere, but be defensive.
}
severeness = v.Code
err = v.Err
case *Bool:
if v.B {
return nil
}
default:
return c.NewErrf("invalid validator %s.%s", b.Package.StringValue(c), b.Name)
}
// failed:
var buf bytes.Buffer
b.WriteName(&buf, c)
if len(args) > 1 {
buf.WriteString("(")
for i, a := range args[1:] {
if i > 0 {
_, _ = buf.WriteString(", ")
}
buf.WriteString(c.Str(a))
}
buf.WriteString(")")
}
vErr := c.NewPosf(src, "invalid value %s (does not satisfy %s)", args[0], buf.String())
for _, v := range args {
vErr.AddPosition(v)
}
return &Bottom{Code: severeness, Err: errors.Wrap(vErr, err)}
}
// A Disjunction represents a disjunction, where each disjunct may or may not
// be marked as a default.
type DisjunctionExpr struct {
Src *ast.BinaryExpr
Values []Disjunct
HasDefaults bool
}
// A Disjunct is used in Disjunction.
type Disjunct struct {
Val Expr
Default bool
}
func (x *DisjunctionExpr) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *DisjunctionExpr) evaluate(c *OpContext) Value {
e := c.Env(0)
v := &Vertex{Conjuncts: []Conjunct{{e, x, CloseInfo{}}}}
c.Unify(v, Finalized) // TODO: also partial okay?
// TODO: if the disjunction result originated from a literal value, we may
// consider the result closed to create more permanent errors.
return v
}
// A Conjunction is a conjunction of values that cannot be represented as a
// single value. It is the result of unification.
type Conjunction struct {
Src ast.Expr
Values []Value
}
func (x *Conjunction) Source() ast.Node { return x.Src }
func (x *Conjunction) Kind() Kind {
k := TopKind
for _, v := range x.Values {
k &= v.Kind()
}
return k
}
// A disjunction is a disjunction of values. It is the result of expanding
// a DisjunctionExpr if the expression cannot be represented as a single value.
type Disjunction struct {
Src ast.Expr
// Values are the non-error disjuncts of this expression. The first
// NumDefault values are default values.
Values []*Vertex
Errors *Bottom // []bottom
// NumDefaults indicates the number of default values.
NumDefaults int
HasDefaults bool
}
func (x *Disjunction) Source() ast.Node { return x.Src }
func (x *Disjunction) Kind() Kind {
k := BottomKind
for _, v := range x.Values {
k |= v.Kind()
}
return k
}
// A ForClause represents a for clause of a comprehension. It can be used
// as a struct or list element.
//
// for k, v in src {}
//
type ForClause struct {
Syntax *ast.ForClause
Key Feature
Value Feature
Src Expr
Dst Yielder
}
func (x *ForClause) Source() ast.Node {
if x.Syntax == nil {
return nil
}
return x.Syntax
}
func (x *ForClause) yield(c *OpContext, f YieldFunc) {
n := c.node(x, x.Src, true, AllArcs)
for _, a := range n.Arcs {
if !a.Label.IsRegular() {
continue
}
c.Unify(a, Partial)
n := &Vertex{status: Finalized}
// TODO: only needed if value label != _
b := &Vertex{
Label: x.Value,
BaseValue: a,
}
n.Arcs = append(n.Arcs, b)
if x.Key != 0 {
v := &Vertex{Label: x.Key}
key := a.Label.ToValue(c)
v.AddConjunct(MakeRootConjunct(c.Env(0), key))
v.SetValue(c, Finalized, key)
n.Arcs = append(n.Arcs, v)
}
sub := c.spawn(n)
saved := c.PushState(sub, x.Dst.Source())
x.Dst.yield(c, f)
if b := c.PopState(saved); b != nil {
c.AddBottom(b)
break
}
if c.HasErr() {
break
}
}
}
// An IfClause represents an if clause of a comprehension. It can be used
// as a struct or list element.
//
// if cond {}
//
type IfClause struct {
Src *ast.IfClause
Condition Expr
Dst Yielder
}
func (x *IfClause) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *IfClause) yield(ctx *OpContext, f YieldFunc) {
if ctx.BoolValue(ctx.value(x.Condition)) {
x.Dst.yield(ctx, f)
}
}
// An LetClause represents a let clause in a comprehension.
//
// for k, v in src {}
//
type LetClause struct {
Src *ast.LetClause
Label Feature
Expr Expr
Dst Yielder
}
func (x *LetClause) Source() ast.Node {
if x.Src == nil {
return nil
}
return x.Src
}
func (x *LetClause) yield(c *OpContext, f YieldFunc) {
n := &Vertex{Arcs: []*Vertex{
{Label: x.Label, Conjuncts: []Conjunct{{c.Env(0), x.Expr, CloseInfo{}}}},
}}
sub := c.spawn(n)
saved := c.PushState(sub, x.Dst.Source())
x.Dst.yield(c, f)
if b := c.PopState(saved); b != nil {
c.AddBottom(b)
}
}
// A ValueClause represents the value part of a comprehension.
type ValueClause struct {
*StructLit
}
func (x *ValueClause) Source() ast.Node {
if x.StructLit == nil {
return nil
}
if x.Src == nil {
return nil
}
return x.Src
}
func (x *ValueClause) yield(op *OpContext, f YieldFunc) {
f(op.Env(0), x.StructLit)
}