cue: implement unary comparators and remove ranges
This introduces a new unification type that accumulates
overallapping values that cannot be represented as a single
value. Before ranges would simplify as they were unified.
This unification type is a generalization of this mechanism.
As a result of this change, interval arithmetic is gone
(was not allowed according to the spec.)
Fixes cuelang/cue#11.
Change-Id: I1a3d21d95a0c02cf0bea6ce3422047e062fa5c6e
diff --git a/cmd/cue/cmd/testdata/trim/trim.cue b/cmd/cue/cmd/testdata/trim/trim.cue
index 362e3c5..7a4b264 100644
--- a/cmd/cue/cmd/testdata/trim/trim.cue
+++ b/cmd/cue/cmd/testdata/trim/trim.cue
@@ -19,7 +19,7 @@
rcList: [{a: "a", c: b}]
t <Name>: {
- x: 0..5
+ x: >=0 & <=5
}
}
@@ -52,7 +52,7 @@
t <Name>: {
// Combined with the other template, we know the value must be 5 and
// thus the entry below can be eliminated.
- x: 5..8
+ x: >=5 & <=8
}
t u: {
diff --git a/cmd/cue/cmd/testdata/trim/trim.out b/cmd/cue/cmd/testdata/trim/trim.out
index 862b8a4..63b5cc2 100644
--- a/cmd/cue/cmd/testdata/trim/trim.out
+++ b/cmd/cue/cmd/testdata/trim/trim.out
@@ -19,7 +19,7 @@
rcList: [{a: "a", c: b}]
t <Name>: {
- x: 0..5
+ x: >=0 & <=5
}
}
@@ -42,7 +42,7 @@
t <Name>: {
// Combined with the other template, we know the value must be 5 and
// thus the entry below can be eliminated.
- x: 5..8
+ x: >=5 & <=8
}
t u: {
diff --git a/cmd/cue/cmd/trim.go b/cmd/cue/cmd/trim.go
index 8265ede..92e0f3d 100644
--- a/cmd/cue/cmd/trim.go
+++ b/cmd/cue/cmd/trim.go
@@ -61,8 +61,8 @@
$ cat <<EOF > foo.cue
light <Name>: {
room: string
- brightnessOff: 0.0 | 0..100.0
- brightnessOn: 100.0 | 0..100.0
+ brightnessOff: 0.0 | >=0 & <=100.0
+ brightnessOn: 100.0 | >=0 & <=100.0
}
light ceiling50: {
@@ -76,8 +76,8 @@
$ cat foo.cue
light <Name>: {
room: string
- brightnessOff: 0.0 | 0..100.0
- brightnessOn: 100.0 | 0..100.0
+ brightnessOff: 0.0 | >=0 & <=100.0
+ brightnessOn: 100.0 | >=0 & <=100.0
}
light ceiling50: {
diff --git a/cue/ast.go b/cue/ast.go
index 013b119..7b067bd 100644
--- a/cue/ast.go
+++ b/cue/ast.go
@@ -417,7 +417,7 @@
}
list.initLit()
if n.Ellipsis != token.NoPos || n.Type != nil {
- list.len = &rangeLit{list.baseValue, list.len, &top{list.baseValue}}
+ list.len = &bound{list.baseValue, opGeq, list.len}
if n.Type != nil {
list.typ = v.walk(n.Type)
}
@@ -457,13 +457,24 @@
value = call
case *ast.UnaryExpr:
- if n.Op == token.MUL {
+ switch n.Op {
+ case token.NOT, token.ADD, token.SUB:
+ value = &unaryExpr{
+ newExpr(n),
+ tokenMap[n.Op],
+ v.walk(n.X),
+ }
+ case token.GEQ, token.GTR, token.LSS, token.LEQ, token.NEQ:
+ value = &bound{
+ newExpr(n),
+ tokenMap[n.Op],
+ v.walk(n.X),
+ }
+
+ case token.MUL:
return v.error(n, "preference mark not allowed at this position")
- }
- value = &unaryExpr{
- newExpr(n),
- tokenMap[n.Op],
- v.walk(n.X),
+ default:
+ return v.error(n, "unsupported unary operator %q", n.Op)
}
case *ast.BinaryExpr:
@@ -473,12 +484,7 @@
v.addDisjunctionElem(d, n.X, false)
v.addDisjunctionElem(d, n.Y, false)
value = d
- case token.RANGE:
- value = &rangeLit{
- newExpr(n),
- v.walk(n.X), // from
- v.walk(n.Y), // to
- }
+
default:
value = &binaryExpr{
newExpr(n),
diff --git a/cue/ast_test.go b/cue/ast_test.go
index 8ae1aff..91ec603 100644
--- a/cue/ast_test.go
+++ b/cue/ast_test.go
@@ -54,6 +54,14 @@
`,
out: "<0>{a: null, b: true, c: false}",
}, {
+ in: `
+ a: <1
+ b: >= 0 & <= 10
+ c: != null
+ d: >100
+ `,
+ out: `<0>{a: <1, b: (>=0 & <=10), c: !=null, d: >100}`,
+ }, {
in: "" +
`a: "\(4)",
b: "one \(a) two \( a + c )",
@@ -117,23 +125,23 @@
in: `
l0: 3*[int]
l0: [1, 2, 3]
- l1: (0..5)*[string]
+ l1: <=5*[string]
l1: ["a", "b"]
- l2: (0..5)*[{ a: int }]
+ l2: (<=5)*[{ a: int }]
l2: [{a: 1}, {a: 2, b: 3}]
- l3: (0..10)*[int]
+ l3: (<=10)*[int]
l3: [1, 2, 3, ...]
l4: [1, 2, ...]
l4: [...int]
l5: [1, ...int]
- s1: ((0..6)*[int])[2:3]
+ s1: ((<=6)*[int])[2:3]
s2: [0,2,3][1:2]
- e0: (2..5)*[{}]
+ e0: (>=2 & <=5)*[{}]
e0: [{}]
`,
- out: `<0>{l0: ((3 * [int]) & [1,2,3]), l1: (((0..5) * [string]) & ["a","b"]), l2: (((0..5) * [<1>{a: int}]) & [<2>{a: 1},<3>{a: 2, b: 3}]), l3: (((0..10) * [int]) & [1,2,3, ...]), l4: ([1,2, ...] & [, ...int]), l5: [1, ...int], s1: ((0..6) * [int])[2:3], s2: [0,2,3][1:2], e0: (((2..5) * [<4>{}]) & [<5>{}])}`,
+ out: `<0>{l0: ((3 * [int]) & [1,2,3]), l1: ((<=5 * [string]) & ["a","b"]), l2: ((<=5 * [<1>{a: int}]) & [<2>{a: 1},<3>{a: 2, b: 3}]), l3: ((<=10 * [int]) & [1,2,3, ...]), l4: ([1,2, ...] & [, ...int]), l5: [1, ...int], s1: (<=6 * [int])[2:3], s2: [0,2,3][1:2], e0: (((>=2 & <=5) * [<4>{}]) & [<5>{}])}`,
}, {
in: `
a: 5 | "a" | true
@@ -227,12 +235,12 @@
out: `<0>{a: [ <1>for _, v in <0>.b yield (*nil*): <1>.v ], b: <2>{a: 1, b: 2, c: 3}}`,
}, {
in: `
- a: 1..2
- b: 1..2..3
- c: "a".."b"
- d: (2+3)..(4+5)
+ a: >=1 & <=2
+ b: >=1 & >=2 & <=3
+ c: >="a" & <"b"
+ d: >(2+3) & <(4+5)
`,
- out: `<0>{a: (1..2), b: ((1..2)..3), c: ("a".."b"), d: ((2 + 3)..(4 + 5))}`,
+ out: `<0>{a: (>=1 & <=2), b: ((>=1 & >=2) & <=3), c: (>="a" & <"b"), d: (>(2 + 3) & <(4 + 5))}`,
}, {
in: `
a: *1,
diff --git a/cue/binop.go b/cue/binop.go
index 409d4a0..62fe166 100644
--- a/cue/binop.go
+++ b/cue/binop.go
@@ -64,6 +64,9 @@
left, right = right, left
}
if op != opUnify {
+ if !kind.isGround() {
+ return ctx.mkErr(src, codeIncomplete, "incomplete error")
+ }
ctx.exprDepth++
v := left.binOp(ctx, src, op, right) // may return incomplete
ctx.exprDepth--
@@ -89,6 +92,10 @@
return distribute(ctx, src, dr, left)
}
+ if _, ok := right.(*unification); ok {
+ return right.binOp(ctx, src, opUnify, left)
+ }
+
// TODO: value may be incomplete if there is a cycle. Instead of an error
// schedule an assert and return the atomic value, if applicable.
v := left.binOp(ctx, src, op, right)
@@ -142,6 +149,58 @@
panic("unreachable: special-cased")
}
+func (x *unification) add(ctx *context, src source, v evaluated) evaluated {
+ for progress := true; progress; {
+ progress = false
+ k := 0
+
+ for i, vx := range x.values {
+ a := binOp(ctx, src, opUnify, vx, v)
+ switch _, isUnify := a.(*unification); {
+ case isBottom(a):
+ if !isIncomplete(a) {
+ return a
+ }
+ fallthrough
+ case isUnify:
+ x.values[k] = x.values[i]
+ k++
+ continue
+ }
+ // k will not be raised in this iteration. So the outer loop
+ // will ultimately terminate as k reaches 0.
+ // In practice it is seems unlikely that there will be more than
+ // two iterations for any addition.
+ // progress = true
+ v = a
+ }
+ if k == 0 {
+ return v
+ }
+ x.values = x.values[:k]
+ }
+ x.values = append(x.values, v)
+ return nil
+}
+
+func (x *unification) binOp(ctx *context, src source, op op, other evaluated) evaluated {
+ if op == opUnify {
+ u := &unification{baseValue: baseValue{src}}
+ u.values = append(u.values, x.values...)
+ if y, ok := other.(*unification); ok {
+ for _, vy := range y.values {
+ if v := u.add(ctx, src, vy); v != nil {
+ return v
+ }
+ }
+ } else if v := u.add(ctx, src, other); v != nil {
+ return v
+ }
+ return u
+ }
+ return ctx.mkIncompatible(src, op, x, other)
+}
+
func (x *top) binOp(ctx *context, src source, op op, other evaluated) evaluated {
switch op {
case opUnify:
@@ -162,9 +221,11 @@
return &basicType{binSrc(src.Pos(), op, x, other), k & typeKinds}
}
}
- case *rangeLit:
+
+ case *bound:
src = mkBin(ctx, src.Pos(), op, x, other)
return ctx.mkErr(src, codeIncomplete, "%s with incomplete values", op)
+
case *numLit:
if op == opUnify {
if k == y.k {
@@ -176,6 +237,7 @@
}
src = mkBin(ctx, src.Pos(), op, x, other)
return ctx.mkErr(src, codeIncomplete, "%s with incomplete values", op)
+
default:
if k&typeKinds != bottomKind {
return other
@@ -184,238 +246,203 @@
return ctx.mkIncompatible(src, op, x, other)
}
-// unifyFrom determines the maximum value of a and b.
-func unifyFrom(ctx *context, src source, a, b evaluated) evaluated {
- if a.kind().isGround() && b.kind().isGround() {
- if leq(ctx, src, a, b) {
- return b
- }
- return a
+func checkBounds(ctx *context, src source, r *bound, op op, a, b evaluated) evaluated {
+ v := binOp(ctx, src, op, a, b)
+ if isBottom(v) || !v.(*boolLit).b {
+ return errOutOfBounds(ctx, src.Pos(), r, a)
}
- if isTop(a) {
- return b
- }
- if isTop(b) {
- return a
- }
- if x, ok := a.(*rangeLit); ok {
- return unifyFrom(ctx, src, x.from.(evaluated), b)
- }
- if x, ok := b.(*rangeLit); ok {
- return unifyFrom(ctx, src, a, x.from.(evaluated))
- }
- src = mkBin(ctx, src.Pos(), opUnify, a, b)
- return ctx.mkErr(src, "incompatible types %v and %v", a.kind(), b.kind())
+ return nil
}
-// unifyTo determines the minimum value of a and b.
-func unifyTo(ctx *context, src source, a, b evaluated) evaluated {
- if a.kind().isGround() && b.kind().isGround() {
- if leq(ctx, src, a, b) {
- return a
- }
- return b
- }
- if isTop(a) {
- return b
- }
- if isTop(b) {
- return a
- }
- if x, ok := a.(*rangeLit); ok {
- return unifyTo(ctx, src, x.to.(evaluated), b)
- }
- if x, ok := b.(*rangeLit); ok {
- return unifyTo(ctx, src, a, x.to.(evaluated))
- }
- src = mkBin(ctx, src.Pos(), opUnify, a, b)
- return ctx.mkErr(src, "incompatible types %v and %v", a.kind(), b.kind())
-}
-
-func errInRange(ctx *context, pos token.Pos, r *rangeLit, v evaluated) *bottom {
+func errOutOfBounds(ctx *context, pos token.Pos, r *bound, v evaluated) *bottom {
if pos == token.NoPos {
pos = r.Pos()
}
- const msgInRange = "value %v not in range %v"
e := mkBin(ctx, pos, opUnify, r, v)
- return ctx.mkErr(e, msgInRange, v.strValue(), debugStr(ctx, r))
+ if r.op == opNeq {
+ const msgInRange = "%v excluded by %v"
+ return ctx.mkErr(e, msgInRange, debugStr(ctx, v), debugStr(ctx, r))
+ }
+ const msgInRange = "%v not within bound %v"
+ return ctx.mkErr(e, msgInRange, debugStr(ctx, v), debugStr(ctx, r))
}
-func (x *rangeLit) binOp(ctx *context, src source, op op, other evaluated) evaluated {
- combine := func(x, y evaluated) evaluated {
- if _, ok := x.(*numLit); !ok {
- return x
- }
- if _, ok := y.(*numLit); !ok {
- return y
- }
- return binOp(ctx, src, op, x, y)
+func opInfo(op op) (cmp op, norm int) {
+ switch op {
+ case opGtr:
+ return opGeq, 1
+ case opGeq:
+ return opGtr, 1
+ case opLss:
+ return opLeq, -1
+ case opLeq:
+ return opLss, -1
+ case opNeq:
+ return opNeq, 0
}
- from := x.from.(evaluated)
- to := x.to.(evaluated)
- newSrc := mkBin(ctx, src.Pos(), op, x, other)
+ panic("cue: unreachable")
+}
+
+func (x *bound) binOp(ctx *context, src source, op op, other evaluated) evaluated {
+ xv := x.value.(evaluated)
+
+ newSrc := binSrc(src.Pos(), op, x, other)
switch op {
case opUnify:
- k := unifyType(x.kind(), other.kind())
- if k&comparableKind != bottomKind {
- switch y := other.(type) {
- case *basicType:
- from := unify(ctx, src, x.from.(evaluated), y)
- to := unify(ctx, src, x.to.(evaluated), y)
- if from == x.from && to == x.to {
- return x
- }
- return &rangeLit{newSrc.base(), from, to}
- case *rangeLit:
- from := unifyFrom(ctx, src, x.from.(evaluated), y.from.(evaluated))
- to := unifyTo(ctx, src, x.to.(evaluated), y.to.(evaluated))
- if from.kind().isGround() && to.kind().isGround() && !leq(ctx, src, from, to) {
- r1 := debugStr(ctx, x)
- r2 := debugStr(ctx, y)
- return ctx.mkErr(newSrc, "non-overlapping ranges %s and %s", r1, r2)
- }
- return ctx.manifest(&rangeLit{newSrc.base(), from, to})
-
- case *numLit:
- if !leq(ctx, src, x.from.(evaluated), y) || !leq(ctx, src, y, x.to.(evaluated)) {
- return errInRange(ctx, src.Pos(), x, y)
- }
- if y.k != k {
- n := *y
- n.k = k
- return &n
- }
- return other
-
- case *durationLit, *stringLit:
- if !leq(ctx, src, x.from.(evaluated), y) || !leq(ctx, src, y, x.to.(evaluated)) {
- return errInRange(ctx, src.Pos(), x, y)
- }
- return other
- }
+ k, _ := matchBinOpKind(opUnify, x.kind(), other.kind())
+ if k == bottomKind {
+ break
}
- // See https://en.wikipedia.org/wiki/Interval_arithmetic.
- case opAdd:
- switch x.kind() & typeKinds {
- case stringKind:
- if !x.from.kind().isGround() || !x.to.kind().isGround() {
- // TODO: return regexp
+ switch y := other.(type) {
+ case *basicType:
+ v := unify(ctx, src, xv, y)
+ if v == xv {
+ return x
+ }
+ return &bound{newSrc.base(), x.op, v}
+
+ case *bound:
+ yv := y.value.(evaluated)
+ if !xv.kind().isGround() || !yv.kind().isGround() {
return ctx.mkErr(newSrc, codeIncomplete, "cannot add incomplete values")
}
- combine := func(x, y evaluated) evaluated {
- if _, ok := x.(*basicType); ok {
- return ctx.mkErr(newSrc, "adding string to non-concrete type")
+
+ cmp, xCat := opInfo(x.op)
+ _, yCat := opInfo(y.op)
+
+ switch {
+ case xCat == yCat:
+ if x.op == opNeq {
+ if test(ctx, x, opEql, xv, yv) {
+ return x
+ }
+ break
}
- if _, ok := y.(*basicType); ok {
+
+ // xCat == yCat && x.op != opNeq
+ // > a & >= b
+ // > a if a >= b
+ // >= b if a < b
+ // > a & > b
+ // > a if a >= b
+ // > b if a < b
+ // >= a & > b
+ // >= a if a > b
+ // > b if a <= b
+ // >= a & >= b
+ // >= a if a > b
+ // >= b if a <= b
+ // inverse is true as well.
+
+ // Tighten bound.
+ if test(ctx, x, cmp, xv, yv) {
return x
}
- return binOp(ctx, src, opAdd, x, y)
- }
- return &rangeLit{
- baseValue: binSrc(src.Pos(), op, x, other),
- from: combine(minNum(from), minNum(other)),
- to: combine(maxNum(to), maxNum(other)),
- }
+ return y
- case intKind, numKind, floatKind:
- return &rangeLit{
- baseValue: binSrc(src.Pos(), op, x, other),
- from: combine(minNum(from), minNum(other)),
- to: combine(maxNum(to), maxNum(other)),
- }
+ case xCat == -yCat:
+ if xCat == -1 {
+ x, y = y, x
+ }
+ a, aOK := x.value.(evaluated).(*numLit)
+ b, bOK := y.value.(evaluated).(*numLit)
- default:
- return ctx.mkErrUnify(src, x, other)
- }
+ if !aOK || !bOK {
+ break
+ }
- case opSub:
- return &rangeLit{
- baseValue: binSrc(src.Pos(), op, x, other),
- from: combine(minNum(from), maxNum(other)),
- to: combine(maxNum(to), minNum(other)),
- }
+ var d apd.Decimal
+ cond, err := apd.BaseContext.Sub(&d, &b.v, &a.v)
+ if cond.Inexact() || err != nil {
+ break
+ }
- case opQuo:
- // See https://en.wikipedia.org/wiki/Interval_arithmetic.
- // TODO: all this is strictly not correct. To do it right we need to
- // have non-inclusive ranges at the least. So for now we just do this.
- var from, to evaluated
- if max := maxNum(other); !max.kind().isGround() {
- from = newNum(other, max.kind()) // 1/infinity is 0
- } else if num, ok := max.(*numLit); ok && num.v.IsZero() {
- from = &basicType{num.baseValue, num.kind()} // div by 0
- } else {
- one := newNum(other, max.kind())
- one.v.SetInt64(1)
- from = combine(one, max)
- }
+ // attempt simplification
+ // numbers
+ // >=a & <=b
+ // a if a == b
+ // _|_ if a < b
+ // >=a & <b
+ // _|_ if b <= a
+ // >a & <=b
+ // _|_ if b <= a
+ // >a & <b
+ // _|_ if b <= a
- if _, ok := other.(*rangeLit); !ok {
- other = from
- } else {
- if min := minNum(other); !min.kind().isGround() {
- to = newNum(other, min.kind()) // 1/infinity is 0
- } else if num, ok := min.(*numLit); ok && num.v.IsZero() {
- to = &basicType{num.baseValue, num.kind()} // div by 0
- } else {
- one := newNum(other, min.kind())
- one.v.SetInt64(1)
- to = combine(one, min)
- }
+ // integers
+ // >=a & <=b
+ // a if b-a == 0
+ // _|_ if a < b
+ // >=a & <b
+ // a if b-a == 1
+ // _|_ if b <= a
+ // >a & <=b
+ // b if b-a == 1
+ // _|_ if b <= a
+ // >a & <b
+ // a+1 if b-a == 2
+ // _|_ if b <= a
- if !from.kind().isGround() && !to.kind().isGround() {
- other = from
- } else if leq(ctx, src, from, to) && leq(ctx, src, to, from) {
- other = from
- } else {
- other = &rangeLit{newSrc.base(), from, to}
- }
- }
- fallthrough
+ switch diff, err := d.Int64(); {
+ case err != nil:
- case opMul:
- xMin, xMax := minNum(from), maxNum(to)
- yMin, yMax := minNum(other), maxNum(other)
+ case diff == 1:
+ if k&floatKind == 0 {
+ if x.op == opGeq && y.op == opLss {
+ return a
+ }
+ if x.op == opGtr && y.op == opLeq {
+ return b
+ }
+ }
- var from, to evaluated
- negMax := func(from, to *evaluated, val, sign evaluated) {
- if !val.kind().isGround() {
- *from = val
- if num, ok := sign.(*numLit); ok && num.v.Negative {
- *to = val
+ case diff == 2:
+ if k&floatKind == 0 && x.op == opGtr && y.op == opLss {
+ apd.BaseContext.Add(&d, d.SetInt64(1), &a.v)
+ n := *a
+ n.k = k
+ n.v = d
+ return &n
+ }
+
+ case diff == 0:
+ if x.op == opGeq && y.op == opLeq {
+ return a
+ }
+ fallthrough
+
+ case d.Negative:
+ return ctx.mkErr(newSrc, "incompatible bounds %v and %v",
+ debugStr(ctx, x), debugStr(ctx, y))
+ }
+
+ case y.op == opNeq:
+ if !test(ctx, x, x.op, yv, xv) {
+ return x
}
}
- }
- negMax(&from, &to, yMin, xMax)
- negMax(&to, &from, yMax, xMin)
- negMax(&from, &to, xMin, yMax)
- negMax(&to, &from, xMax, yMin)
- if from != nil && to != nil {
- return binOp(ctx, src, opUnify, from, to)
- }
+ return &unification{newSrc, []evaluated{x, y}}
- values := []evaluated{}
- add := func(a, b evaluated) {
- if a.kind().isGround() && b.kind().isGround() {
- values = append(values, combine(a, b))
+ case *numLit:
+ if err := checkBounds(ctx, src, x, x.op, y, xv); err != nil {
+ return err
}
- }
- add(xMin, yMin)
- add(xMax, yMin)
- add(xMin, yMax)
- add(xMax, yMax)
- sort.Slice(values, func(i, j int) bool {
- return !leq(ctx, src, values[j], values[i])
- })
+ // Narrow down number type.
+ if y.k != k {
+ n := *y
+ n.k = k
+ return &n
+ }
+ return other
- r := &rangeLit{baseValue: binSrc(src.Pos(), op, x, other), from: from, to: to}
- if from == nil {
- r.from = values[0]
+ case *nullLit, *boolLit, *durationLit, *list, *structLit, *stringLit, *bytesLit:
+ // All remaining concrete types. This includes non-comparable types
+ // for comparison to null.
+ if err := checkBounds(ctx, src, x, x.op, y, xv); err != nil {
+ return err
+ }
+ return y
}
- if to == nil {
- r.to = values[len(values)-1]
- }
- return r
}
return ctx.mkIncompatible(src, op, x, other)
}
@@ -521,6 +548,13 @@
return x
}
+ case *bound:
+ // Not strictly necessary, but handling this results in better error
+ // messages.
+ if op == opUnify {
+ return other.binOp(ctx, src, opUnify, x)
+ }
+
default:
switch op {
case opEql:
@@ -610,6 +644,14 @@
return ctx.mkIncompatible(src, op, x, other)
}
+func test(ctx *context, src source, op op, a, b evaluated) bool {
+ v := binOp(ctx, src, op, a, b)
+ if isBottom(v) {
+ return false
+ }
+ return v.(*boolLit).b
+}
+
func leq(ctx *context, src source, a, b evaluated) bool {
if isTop(a) || isTop(b) {
return true
@@ -621,42 +663,35 @@
return v.(*boolLit).b
}
-func maxNum(v evaluated) evaluated {
+// TODO: should these go?
+func maxNum(v value) value {
switch x := v.(type) {
case *numLit:
return x
- case *rangeLit:
- return maxNum(x.to.(evaluated))
+ case *bound:
+ switch x.op {
+ case opLeq:
+ return x.value
+ case opLss:
+ return &binaryExpr{x.baseValue, opSub, x.value, one}
+ }
+ return &basicType{x.baseValue, intKind}
}
return v
}
-func minNum(v evaluated) evaluated {
+func minNum(v value) value {
switch x := v.(type) {
case *numLit:
return x
- case *rangeLit:
- return minNum(x.from.(evaluated))
- }
- return v
-}
-
-func maxNumRaw(v value) value {
- switch x := v.(type) {
- case *numLit:
- return x
- case *rangeLit:
- return maxNumRaw(x.to)
- }
- return v
-}
-
-func minNumRaw(v value) value {
- switch x := v.(type) {
- case *numLit:
- return x
- case *rangeLit:
- return minNumRaw(x.from)
+ case *bound:
+ switch x.op {
+ case opGeq:
+ return x.value
+ case opGtr:
+ return &binaryExpr{x.baseValue, opAdd, x.value, one}
+ }
+ return &basicType{x.baseValue, intKind}
}
return v
}
@@ -690,13 +725,6 @@
if op == opUnify {
return y.binOp(ctx, src, op, x)
}
- // infinity math
- // 4 * int = int
- case *rangeLit:
- if op == opUnify {
- return y.binOp(ctx, src, op, x)
- }
- // 5..7 - 8 = -3..4
case *numLit:
k := unifyType(x.kind(), y.kind())
n := newNumBin(k, x, y)
@@ -843,6 +871,7 @@
if !ok {
break
}
+
n := unify(ctx, src, x.len.(evaluated), y.len.(evaluated))
if isBottom(n) {
src = mkBin(ctx, src.Pos(), op, x, other)
diff --git a/cue/debug.go b/cue/debug.go
index c51cc1d..d2d1807 100644
--- a/cue/debug.go
+++ b/cue/debug.go
@@ -204,6 +204,15 @@
writef(" %v ", x.op)
p.debugStr(x.right)
write(")")
+ case *unification:
+ write("(")
+ for i, v := range x.values {
+ if i != 0 {
+ writef(" & ")
+ }
+ p.debugStr(v)
+ }
+ write(")")
case *disjunction:
write("(")
for i, v := range x.values {
@@ -312,12 +321,9 @@
}
case *durationLit:
write(x.d.String())
- case *rangeLit:
- write("(")
- p.debugStr(x.from)
- write("..")
- p.debugStr(x.to)
- write(")")
+ case *bound:
+ p.writef("%v", x.op)
+ p.debugStr(x.value)
case *interpolation:
for i, e := range x.parts {
if i != 0 {
@@ -327,13 +333,13 @@
}
case *list:
// TODO: do not evaluate
- max := maxNum(x.len.evalPartial(p.ctx))
+ max := maxNum(x.len).evalPartial(p.ctx)
inCast := false
ellipsis := false
n, ok := max.(*numLit)
if !ok {
// TODO: do not evaluate
- min := minNum(x.len.evalPartial(p.ctx))
+ min := minNum(x.len).evalPartial(p.ctx)
n, _ = min.(*numLit)
}
ln := 0
@@ -341,8 +347,13 @@
x, _ := n.v.Int64()
ln = int(x)
}
+ open := false
+ switch max.(type) {
+ case *top, *basicType:
+ open = true
+ }
if !ok || ln > len(x.a) {
- if !isTop(max) && !isTop(x.typ) {
+ if !open && !isTop(x.typ) {
p.debugStr(x.len)
write("*[")
p.debugStr(x.typ)
diff --git a/cue/eval.go b/cue/eval.go
index 93efbd3..8832ffc 100644
--- a/cue/eval.go
+++ b/cue/eval.go
@@ -168,44 +168,19 @@
return e.err(err)
}
-func (x *rangeLit) evalPartial(ctx *context) (result evaluated) {
+func (x *bound) evalPartial(ctx *context) (result evaluated) {
if ctx.trace {
- defer uni(indent(ctx, "rangeLit", x))
+ defer uni(indent(ctx, "bound", x))
defer func() { ctx.debugPrint("result:", result) }()
}
- rngFrom := x.from.evalPartial(ctx)
- rngTo := x.to.evalPartial(ctx)
- // rngFrom := ctx.manifest(x.from)
- // rngTo := ctx.manifest(x.to)
- // kind := unifyType(rngFrom.kind(), rngTo.kind())
- // TODO: sufficient to do just this?
- kind, _ := matchBinOpKind(opLeq, rngFrom.kind(), rngTo.kind())
- if kind&comparableKind == bottomKind {
- return ctx.mkErr(x, "invalid range: must be defined for strings or numbers")
+ v := x.value.evalPartial(ctx)
+ if isBottom(v) {
+ return ctx.mkErr(x, v, "error evaluating bound")
}
- // Collapse evaluated nested ranges
- if from, ok := rngFrom.(*rangeLit); ok {
- rngFrom = from.from.(evaluated)
+ if v == x.value {
+ return x
}
- if to, ok := rngTo.(*rangeLit); ok {
- rngTo = to.to.(evaluated)
- }
- rng := &rangeLit{x.baseValue, rngFrom, rngTo}
- if !rngFrom.kind().isGround() || !rngTo.kind().isGround() {
- return rng
- }
- // validate range
- comp := binOp(ctx, x, opLeq, rngFrom, rngTo)
- if isBottom(comp) {
- return ctx.mkErr(comp, "invalid range")
- }
- if !comp.(*boolLit).b {
- return ctx.mkErr(x, "for ranges from <= to, found %v > %v", rngFrom, rngTo)
- }
- if binOp(ctx, x, opEql, rngFrom, rngTo).(*boolLit).b {
- return rngFrom
- }
- return rng
+ return &bound{x.baseValue, x.op, v}
}
func (x *interpolation) evalPartial(ctx *context) (result evaluated) {
@@ -302,6 +277,11 @@
return x
}
+func (x *unification) evalPartial(ctx *context) (result evaluated) {
+ // By definition, all of the values in this type are already evaluated.
+ return x
+}
+
func (x *disjunction) evalPartial(ctx *context) (result evaluated) {
if ctx.trace {
defer uni(indent(ctx, "disjunction", x))
@@ -460,10 +440,6 @@
return &basicType{v.baseValue, numeric | nonGround}
case *basicType:
return &basicType{v.baseValue, (v.k & numeric) | nonGround}
- case *rangeLit:
- from := evalUnary(ctx, src, op, v.from)
- to := evalUnary(ctx, src, op, v.to)
- return &rangeLit{src.base(), from, to}
}
case opNot:
diff --git a/cue/export.go b/cue/export.go
index 15c0b04..3366d9b 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -99,6 +99,18 @@
X: p.expr(x.left),
Op: opMap[x.op], Y: p.expr(x.right),
}
+ case *bound:
+ return &ast.UnaryExpr{Op: opMap[x.op], X: p.expr(x.value)}
+ case *unification:
+ if len(x.values) == 1 {
+ return p.expr(x.values[0])
+ }
+ bin := p.expr(x.values[0])
+ for _, v := range x.values[1:] {
+ bin = &ast.BinaryExpr{X: bin, Op: token.UNIFY, Y: p.expr(v)}
+ }
+ return bin
+
case *disjunction:
if len(x.values) == 1 {
return p.expr(x.values[0].val)
@@ -110,12 +122,8 @@
}
return e
}
- bin := &ast.BinaryExpr{
- X: expr(x.values[0]),
- Op: token.DISJUNCTION,
- Y: expr(x.values[1]),
- }
- for _, v := range x.values[2:] {
+ bin := expr(x.values[0])
+ for _, v := range x.values[1:] {
bin = &ast.BinaryExpr{X: bin, Op: token.DISJUNCTION, Y: expr(v)}
}
return bin
@@ -203,13 +211,6 @@
case *durationLit:
panic("unimplemented")
- case *rangeLit:
- return &ast.BinaryExpr{
- X: p.expr(x.from),
- Op: token.RANGE,
- Y: p.expr(x.to),
- }
-
case *interpolation:
t := &ast.Interpolation{}
multiline := false
@@ -257,10 +258,10 @@
for _, e := range x.a {
list.Elts = append(list.Elts, p.expr(e))
}
- max := maxNumRaw(x.len)
+ max := maxNum(x.len)
num, ok := max.(*numLit)
if !ok {
- min := minNumRaw(x.len)
+ min := minNum(x.len)
num, _ = min.(*numLit)
}
ln := 0
@@ -268,9 +269,14 @@
x, _ := num.v.Int64()
ln = int(x)
}
+ open := false
+ switch max.(type) {
+ case *top, *basicType:
+ open = true
+ }
if !ok || ln > len(x.a) {
list.Type = p.expr(x.typ)
- if !isTop(max) && !isTop(x.typ) {
+ if !open && !isTop(x.typ) {
expr = &ast.BinaryExpr{
X: &ast.BinaryExpr{
X: p.expr(x.len),
diff --git a/cue/export_test.go b/cue/export_test.go
index f5dd274..2673305 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -74,11 +74,11 @@
in: `{
a: 5*[int]
a: [1, 2, ...]
- b: 0..5*[int]
+ b: <=5*[int]
b: [1, 2, ...]
- c: 3..5*[int]
+ c: (>=3 & <=5)*[int]
c: [1, 2, ...]
- d: 2.._*[int]
+ d: >=2*[int]
d: [1, 2, ...]
e: [...int]
e: [1, 2, ...]
@@ -87,16 +87,16 @@
out: unindent(`
{
a: 5*[int] & [1, 2, ...int]
- b: 2..5*[int] & [1, 2, ...int]
- c: 3..5*[int] & [1, 2, ...int]
+ b: (>=2 & <=5)*[int] & [1, 2, ...int]
+ c: (<=5 & >=3)*[int] & [1, 2, ...int]
d: [1, 2, ...int]
e: [1, 2, ...int]
f: [1, 2, ...]
}`),
}, {
in: `{
- a: (0.._)*[int]
- a: (0.._)*[...int]
+ a: >=0*[int]
+ a: [...int]
}`,
out: unindent(`
{
@@ -120,6 +120,23 @@
b: a[2:3]
}`),
}, {
+ in: `{
+ a: >=0 & <=10 & !=1
+ }`,
+ out: unindent(`
+ {
+ a: >=0 & <=10 & !=1
+ }`),
+ }, {
+ raw: true,
+ in: `{
+ a: >=0 & <=10 & !=1
+ }`,
+ out: unindent(`
+ {
+ a: >=0 & <=10 & !=1
+ }`),
+ }, {
raw: true,
in: `{ a: [1, 2], b: { "\(k)": v for k, v in a if a > 1 } }`,
out: unindent(`
@@ -139,10 +156,10 @@
}`),
}, {
raw: true,
- in: `{ a: 0..10, b: "Count: \(a) times" }`,
+ in: `{ a: >=0 & <=10, b: "Count: \(a) times" }`,
out: unindent(`
{
- a: 0..10
+ a: >=0 & <=10
b: "Count: \(a) times"
}`),
}, {
diff --git a/cue/format/node.go b/cue/format/node.go
index f9cef31..85035a5 100644
--- a/cue/format/node.go
+++ b/cue/format/node.go
@@ -310,9 +310,6 @@
f.internalError("depth < 1:", depth)
depth = 1
}
- if prec1 == 8 { // ..
- prec1 = 9 // always parentheses for values of ranges
- }
f.binaryExpr(x, prec1, cutoff(x, depth), depth)
case *ast.UnaryExpr:
@@ -556,7 +553,6 @@
// (Algorithm suggestion by Russ Cox.)
//
// The precedences are:
-// 8 ..
// 7 * / % quo rem div mod
// 6 + -
// 5 == != < <= > >=
diff --git a/cue/format/testdata/expressions.golden b/cue/format/testdata/expressions.golden
index ed5f3bf..15c6377 100644
--- a/cue/format/testdata/expressions.golden
+++ b/cue/format/testdata/expressions.golden
@@ -29,10 +29,10 @@
e: 1 + 2*3
e: 1 * 2 * 3 // error
- e: 2..3
- e: 2..(3 + 4)
- ex: 2..3 + 4*5
- e: (2..3)..4
+ e: >=2 & <=3
+ e: >2 & <=(3 + 4)
+ ex: >2 & <=(3 + 4*5)
+ e: >2 & <=3 & <=4
e: 1 + 2 + 3 // error
e: s[1+2]
diff --git a/cue/format/testdata/expressions.input b/cue/format/testdata/expressions.input
index 60f513a..6b324a3 100644
--- a/cue/format/testdata/expressions.input
+++ b/cue/format/testdata/expressions.input
@@ -29,10 +29,10 @@
e: 1+2*3
e: 1*2*3 // error
- e: 2..3
- e: 2..(3 + 4)
- ex: 2..3+4*5
- e: 2..3..4
+ e: >=2 & <=3
+ e: >2 & <=(3 + 4)
+ ex: >2 & <=(3 + 4*5)
+ e: >2 & <=3 & <=4
e: 1 + 2 + 3 // error
e: s[1+2]
diff --git a/cue/kind.go b/cue/kind.go
index 86a16d0..a35e886 100644
--- a/cue/kind.go
+++ b/cue/kind.go
@@ -232,7 +232,7 @@
case a&nonGround == 0 && b&nonGround == 0:
// both ground values: nothing to do
- case op != opUnify && op != opLand && op != opLor:
+ case op != opUnify && op != opLand && op != opLor && op != opNeq:
default:
invert = aGround && !bGround
diff --git a/cue/op.go b/cue/op.go
index 9a2a809..9d9c0a8 100644
--- a/cue/op.go
+++ b/cue/op.go
@@ -67,8 +67,6 @@
opIMod
opIQuo
opIRem
-
- opRange // Used in computedSource
)
var opStrings = []string{
@@ -99,8 +97,6 @@
opIMod: "mod",
opIQuo: "quo",
opIRem: "rem",
-
- opRange: "..",
}
func (op op) String() string { return opStrings[op] }
@@ -131,8 +127,6 @@
token.NEQ: opNeq, // !=
token.LEQ: opLeq, // <=
token.GEQ: opGeq, // >=
-
- token.RANGE: opRange, // ..
}
var opMap = map[op]token.Token{}
diff --git a/cue/parser/parser.go b/cue/parser/parser.go
index 13870ec..b46fa25 100644
--- a/cue/parser/parser.go
+++ b/cue/parser/parser.go
@@ -425,12 +425,6 @@
return true // "insert" comma and continue
}
-func assert(cond bool, msg string) {
- if !cond {
- panic("lacelang/parser internal error: " + msg)
- }
-}
-
// syncExpr advances to the next field in a field list.
// Used for synchronization after an error.
func syncExpr(p *parser) {
@@ -1109,7 +1103,8 @@
}
switch p.tok {
- case token.ADD, token.SUB, token.NOT, token.MUL:
+ case token.ADD, token.SUB, token.NOT, token.MUL,
+ token.NEQ, token.LSS, token.LEQ, token.GEQ, token.GTR:
pos, op := p.pos, p.tok
c := p.openComments()
p.next()
@@ -1228,18 +1223,6 @@
return x
}
-func (p *parser) parseCallExpr(callType string) *ast.CallExpr {
- x := p.parseRHS() // could be a conversion: (some type)(x)
- if call, isCall := x.(*ast.CallExpr); isCall {
- return call
- }
- if _, isBad := x.(*ast.BadExpr); !isBad {
- // only report error if it's a new one
- p.error(p.safePos(x.End()), fmt.Sprintf("function must be invoked in %s statement", callType))
- }
- return nil
-}
-
// ----------------------------------------------------------------------------
// Declarations
diff --git a/cue/parser/parser_test.go b/cue/parser/parser_test.go
index e0c5a69..fdae715 100644
--- a/cue/parser/parser_test.go
+++ b/cue/parser/parser_test.go
@@ -143,14 +143,14 @@
"a: (2 div 3) mod 5, b: (2 quo 3) rem 4, c: 2 div 3 div 4",
}, {
"ranges",
- ` a: 1..2
- b: 2.0 .. 40.0
- c: "a".."b"
- v: (1..2)..(5..10)
- w: 1..2..3
- d: 3T..5M
+ ` a: >=1 & <=2
+ b: >2.0 & <= 40.0
+ c: >"a" & <="b"
+ v: (>=1 & <=2) & <=(>=5 & <=10)
+ w: >1 & <=2 & <=3
+ d: >=3T & <=5M
`,
- "a: 1..2, b: 2.0..40.0, c: \"a\"..\"b\", v: (1..2)..(5..10), w: 1..2..3, d: 3T..5M",
+ "a: >=1&<=2, b: >2.0&<=40.0, c: >\"a\"&<=\"b\", v: (>=1&<=2)&<=(>=5&<=10), w: >1&<=2&<=3, d: >=3T&<=5M",
}, {
"indices",
`{
@@ -183,12 +183,12 @@
"list types",
`{
a: 4*[int]
- b: 0..5*[ {a: 5} ]
+ b: <=5*[ {a: 5} ]
c1: [...int]
c2: [...]
c3: [1, 2, ...int,]
}`,
- `{a: 4*[int], b: 0..5*[{a: 5}], c1: [...int], c2: [...], c3: [1, 2, ...int]}`,
+ `{a: 4*[int], b: <=5*[{a: 5}], c1: [...int], c2: [...], c3: [1, 2, ...int]}`,
}, {
"list comprehensions",
`{
diff --git a/cue/resolve_test.go b/cue/resolve_test.go
index d9bef3a..bfac4ee 100644
--- a/cue/resolve_test.go
+++ b/cue/resolve_test.go
@@ -231,10 +231,10 @@
e: [] & 4
e2: [3]["d"]
e3: [3][-1]
- e4: [1, 2, ...4..5] & [1, 2, 4, 8]
- e5: [1, 2, 4, 8] & [1, 2, ...4..5]
+ e4: [1, 2, ...>=4 & <=5] & [1, 2, 4, 8]
+ e5: [1, 2, 4, 8] & [1, 2, ...>=4 & <=5]
`,
- out: `<0>{list: [1,2,3], index: 2, unify: [1,2,3], e: _|_(([] & 4):unsupported op &(list, number)), e2: _|_("d":invalid list index "d" (type string)), e3: _|_(-1:invalid list index -1 (index must be non-negative)), e4: _|_(((4..5) & 8):value 8 not in range (4..5)), e5: _|_(((4..5) & 8):value 8 not in range (4..5))}`,
+ out: `<0>{list: [1,2,3], index: 2, unify: [1,2,3], e: _|_(([] & 4):unsupported op &(list, number)), e2: _|_("d":invalid list index "d" (type string)), e3: _|_(-1:invalid list index -1 (index must be non-negative)), e4: _|_((<=5 & 8):8 not within bound <=5), e5: _|_((<=5 & 8):8 not within bound <=5)}`,
}, {
desc: "selecting",
in: `
@@ -274,7 +274,7 @@
o9: (2 | 3) & (1 | 2 | 3)
o10: (3 | 2) & (1 | *2 | 3)
- m1: (*1 | (*2 | 3)) & 2..3
+ m1: (*1 | (*2 | 3)) & (>=2 & <=3)
m2: (*1 | (*2 | 3)) & (2 | 3)
m3: (*1 | *(*2 | 3)) & (2 | 3)
m4: (2 | 3) & (*2 | 3)
@@ -462,6 +462,92 @@
`,
out: `<0>{a: true, b: true, c: false, d: true, e: false, f: true}`,
}, {
+ desc: "bounds",
+ in: `
+ i1: >1 & 5
+ i2: (>=0 & <=10) & 5
+ i3: !=null & []
+ i4: !=2 & !=4
+
+
+ s1: >=0 & <=10 & !=1 // no simplification
+ s2: >=0 & <=10 & !=11 // >=0 & <=10
+ s3: >5 & !=5 // >5
+ s4: <10 & !=10 // <10
+ s5: !=2 & !=2
+
+ s10: >=0 & <=10 & <12 & >1 // >1 & <=10
+ s11: >0 & >=0 & <=12 & <12 // >0 & <12
+
+ s20: >=10 & <=10 // 10
+
+ s22: >5 & <=6 // no simplification
+ s22a: >5 & (<=6 & int) // 6
+ s22b: (int & >5) & <=6 // 6
+ s22c: >=5 & (<6 & int) // 5
+ s22d: (int & >=5) & <6 // 5
+ s22e: (>=5 & <6) & int // 5
+ s22f: int & (>=5 & <6) // 5
+
+ s23: >0 & <2 // no simplification
+ s23a: (>0 & <2) & int // int & 1
+ s23b: int & (>0 & <2) // int & 1
+ s23c: (int & >0) & <2 // int & 1
+ s23d: >0 & (int & <2) // int & 1
+ s23e: >0.0 & <2.0 // no simplification
+
+ s30: >0 & int
+
+ e1: null & !=null
+ e2: !=null & null
+ e3: >1 & 1
+ e4: <0 & 0
+ e5: >1 & <0
+ e6: >11 & <11
+ e7: >=11 & <11
+ e8: >11 & <=11
+ e9: >"a" & <1
+ `,
+ out: `<0>{i1: 5, i2: 5, i3: [], i4: (!=2 & !=4), ` +
+
+ `s1: (>=0 & <=10 & !=1), ` +
+ `s2: (>=0 & <=10), ` +
+ `s3: >5, ` +
+ `s4: <10, ` +
+ `s5: !=2, ` +
+
+ `s10: (<=10 & >1), ` +
+ `s11: (>0 & <12), ` +
+
+ `s20: 10, ` +
+
+ `s22: (>5 & <=6), ` +
+ `s22a: 6, ` +
+ `s22b: 6, ` +
+ `s22c: 5, ` +
+ `s22d: 5, ` +
+ `s22e: 5, ` +
+ `s22f: 5, ` +
+
+ `s23: (>0 & <2), ` +
+ `s23a: 1, ` +
+ `s23b: 1, ` +
+ `s23c: 1, ` +
+ `s23d: 1, ` +
+ `s23e: (>0.0 & <2.0), ` +
+
+ `s30: >0, ` +
+
+ `e1: _|_((!=null & null):null excluded by !=null), ` +
+ `e2: _|_((!=null & null):null excluded by !=null), ` +
+ `e3: _|_((>1 & 1):1 not within bound >1), ` +
+ `e4: _|_((<0 & 0):0 not within bound <0), ` +
+ `e5: _|_(incompatible bounds >1 and <0), ` +
+ `e6: _|_(incompatible bounds >11 and <11), ` +
+ `e7: _|_(incompatible bounds >=11 and <11), ` +
+ `e8: _|_(incompatible bounds >11 and <=11), ` +
+ `e9: _|_((>"a" & <1):unsupported op &((string)*, (number)*))}`,
+ }, {
desc: "null coalescing",
in: `
a: null
@@ -553,40 +639,39 @@
in: `
l0: 3*[int]
l0: [1, 2, 3]
- l1:(0..5)*[string]
+ l1: <=5*[string]
l1: ["a", "b"]
- l2: (0..5)*[{ a: int }]
+ l2: <=5*[{ a: int }]
l2: [{a: 1}, {a: 2, b: 3}]
- l3: (0..10)*[int]
- l3: [1, 2, 3, ...]
- s1: ((0..6)*[int])[2:3] // TODO: simplify 1*[int] to [int]
+ // TODO: work out a decent way to specify length ranges of lists.
+ // l3: <=10*[int]
+ // l3: [1, 2, 3, ...]
+
+ s1: (<=6*[int])[2:3] // TODO: simplify 1*[int] to [int]
s2: [0,2,3][1:2]
- i1: ((0..6)*[int])[2]
+ i1: (<=6*[int])[2]
i2: [0,2,3][2]
t0: [...{a: 8}]
t0: [{}]
- e0: (2..5)*[{}]
+ e0: >=2*[{}]
e0: [{}]
-
- e1: 0.._*[...int]
`,
- out: `<0>{l0: [1,2,3], l1: ["a","b"], l2: [<1>{a: 1},<2>{a: 2, b: 3}], l3: (3..10)*[int]([1,2,3, ...int]), s1: 1*[int], s2: [2], i1: int, i2: 3, t0: [<3>{a: 8}], e0: _|_(((2..5)*[<4>{}] & [<5>{}]):incompatible list lengths: value 1 not in range (2..5)), e1: [, ...int]}`,
+ out: `<0>{l0: [1,2,3], l1: ["a","b"], l2: [<1>{a: 1},<2>{a: 2, b: 3}], s1: 1*[int], s2: [2], i1: int, i2: 3, t0: [<3>{a: 8}], e0: _|_(([, ...<4>{}] & [<5>{}]):incompatible list lengths: 1 not within bound >=2)}`,
}, {
desc: "list arithmetic",
in: `
l0: 3*[1, 2, 3]
l1: 0*[1, 2, 3]
l2: 10*[]
- l3: (0..2)*[]
- l4: (0..2)*[int]
- l5: (0..2)*(int*[int])
- l6: 3*((3..4)*[int])
+ l3: <=2*[]
+ l4: <=2*[int]
+ l5: <=2*(int*[int])
`,
- out: `<0>{l0: [1,2,3,1,2,3,1,2,3], l1: [], l2: [], l3: [], l4: (0..2)*[int], l5: (0..2)*[int], l6: (9..12)*[int]}`,
+ out: `<0>{l0: [1,2,3,1,2,3,1,2,3], l1: [], l2: [], l3: [], l4: <=2*[int], l5: <=2*[int]}`,
}, {
desc: "correct error messages",
// Tests that it is okay to partially evaluate structs.
@@ -694,98 +779,93 @@
`,
out: `<0>{a: <1>{<>: <2>(name: string)->int, k: 1}, b: <3>{<>: <4>(x: string)->(<5>{x: 0, y: (1 | int)} & <6>{}), v: <7>{x: 0, y: (1 | int)}, w: <8>{x: 0, y: (1 | int)}}, c: <9>{<>: <10>(Name: string)-><11>{name: <10>.Name, y: 1}, foo: <12>{name: "foo", y: 1}, bar: <13>{name: "bar", y: 1}}}`,
}, {
- desc: "simple ranges",
- in: `
- a: 1..2
- c: "a".."b"
- d: (2+3)..(4+5) // 5..9
-
- s1: 1..1 // 1
- s2: 1..2..3 // simplify (1..2)..3 to 1..3
- s3: (1..10)..5 // This is okay!
- s4: 5..(1..10) // This is okay!
- s5: (0..(5..6))..(1..10)
- `,
- out: `<0>{a: (1..2), c: ("a".."b"), d: (5..9), s1: 1, s2: (1..3), s3: (1..5), s4: (5..10), s5: (0..10)}`,
- }, {
desc: "range unification",
in: `
// with concrete values
- a1: 1..5 & 3
- a2: 1..5 & 1
- a3: 1..5 & 5
- a4: 1..5 & 6
- a5: 1..5 & 0
+ a1: >=1 & <=5 & 3
+ a2: >=1 & <=5 & 1
+ a3: >=1 & <=5 & 5
+ a4: >=1 & <=5 & 6
+ a5: >=1 & <=5 & 0
- a6: 3 & 1..5
- a7: 1 & 1..5
- a8: 5 & 1..5
- a9: 6 & 1..5
- a10: 0 & 1..5
+ a6: 3 & >=1 & <=5
+ a7: 1 & >=1 & <=5
+ a8: 5 & >=1 & <=5
+ a9: 6 & >=1 & <=5
+ a10: 0 & >=1 & <=5
// with ranges
- b1: 1..5 & 1..5
- b2: 1..5 & 1..1
- b3: 1..5 & 5..5
- b4: 1..5 & 2..3
- b5: 1..5 & 3..9
- b6: 1..5 & 5..9
- b7: 1..5 & 6..9
+ b1: >=1 & <=5 & >=1 & <=5
+ b2: >=1 & <=5 & >=1 & <=1
+ b3: >=1 & <=5 & >=5 & <=5
+ b4: >=1 & <=5 & >=2 & <=3
+ b5: >=1 & <=5 & >=3 & <=9
+ b6: >=1 & <=5 & >=5 & <=9
+ b7: >=1 & <=5 & >=6 & <=9
- b8: 1..5 & 1..5
- b9: 1..1 & 1..5
- b10: 5..5 & 1..5
- b11: 2..3 & 1..5
- b12: 3..9 & 1..5
- b13: 5..9 & 1..5
- b14: 6..9 & 1..5
+ b8: >=1 & <=5 & >=1 & <=5
+ b9: >=1 & <=1 & >=1 & <=5
+ b10: >=5 & <=5 & >=1 & <=5
+ b11: >=2 & <=3 & >=1 & <=5
+ b12: >=3 & <=9 & >=1 & <=5
+ b13: >=5 & <=9 & >=1 & <=5
+ b14: >=6 & <=9 & >=1 & <=5
// ranges with more general types
- c1: int & 1..5
- c2: 1..5 & int
- c3: string & 1..5
- c4: 1..5 & string
+ c1: int & >=1 & <=5
+ c2: >=1 & <=5 & int
+ c3: string & >=1 & <=5
+ c4: >=1 & <=5 & string
// other types
- s1: "d" .. "z" & "e"
- s2: "d" .. "z" & "ee"
+ s1: >="d" & <="z" & "e"
+ s2: >="d" & <="z" & "ee"
- n1: number & 1..2
- n2: int & 1.1 .. 1.3
- n3: 1.0..3.0 & 2
- n4: 0.0..0.1 & 0.09999
- n5: 1..5 & 2.5
+ n1: number & >=1 & <=2
+ n2: int & >=1.1 & <=1.3
+ n3: >=1.0 & <=3.0 & 2
+ n4: >=0.0 & <=0.1 & 0.09999
+ n5: >=1 & <=5 & 2.5
`,
- out: `<0>{a1: 3, a2: 1, a3: 5, a4: _|_(((1..5) & 6):value 6 not in range (1..5)), a5: _|_(((1..5) & 0):value 0 not in range (1..5)), a6: 3, a7: 1, a8: 5, a9: _|_(((1..5) & 6):value 6 not in range (1..5)), a10: _|_(((1..5) & 0):value 0 not in range (1..5)), b1: (1..5), b2: 1, b3: 5, b4: (2..3), b5: (3..5), b6: 5, b7: _|_(((1..5) & (6..9)):non-overlapping ranges (1..5) and (6..9)), b8: (1..5), b9: 1, b10: 5, b11: (2..3), b12: (3..5), b13: 5, b14: _|_(((6..9) & (1..5)):non-overlapping ranges (6..9) and (1..5)), c1: (1..5), c2: (1..5), c3: _|_((string & (1..5)):unsupported op &((string)*, (number)*)), c4: _|_(((1..5) & string):unsupported op &((number)*, (string)*)), s1: "e", s2: "ee", n1: (1..2), n2: _|_((int & (1.1..1.3)):unsupported op &((int)*, (float)*)), n3: 2, n4: 0.09999, n5: 2.5}`,
- }, {
- desc: "range arithmetic",
- in: `
- r0: (1..2) * (4..5)
- r1: (1..2) * (-1..2)
- r2: (1.0..2.0) * (-0.5..1.0)
- r3: (1..2) + (4..5)
+ out: `<0>{` +
+ `a1: 3, ` +
+ `a2: 1, ` +
+ `a3: 5, ` +
+ `a4: _|_((<=5 & 6):6 not within bound <=5), ` +
+ `a5: _|_((>=1 & 0):0 not within bound >=1), ` +
+ `a6: 3, ` +
+ `a7: 1, ` +
+ `a8: 5, ` +
- i0: (1..2) * 2
- i1: (2..3) * -2
- i2: (1..2) * 2
- i3: (2..3) * -2
+ // TODO: improve error
+ `a9: _|_((6 & <=5):unsupported op &(number, (number)*)), ` +
+ `a10: _|_((0 & >=1):unsupported op &(number, (number)*)), ` +
- t0: int * (1..2) // TODO: should be int
- t1: (1..2) * int
- t2: (1..2) * (0..int)
- t3: (1..int) * (0..2)
- t4: (1..int) * (-1..2)
- t5: _ * (1..2) // TODO: should be int
-
- s0: (1..2) - (3..5)
- s1: (1..2) - 1
-
- str0: ("ab".."cd") + "ef"
- str1: ("ab".."cd") + ("ef".."gh")
- str2: ("ab".."cd") + string
-
- `,
- out: `<0>{r0: (4..10), r1: (-2..4), r2: (-1.00..2.00), r3: (5..7), i0: (2..4), i1: (-6..-4), i2: (2..4), i3: (-6..-4), t0: (int * (1..2)), t1: int, t2: (0..int), t3: (0..int), t4: int, t5: _|_((_ * (1..2)):binary operation on non-ground top value), s0: (-4..-1), s1: (0..1), str0: ("abef".."cdef"), str1: ("abef".."cdgh"), str2: ("ab".."cd")}`,
+ `b1: (>=1 & <=5), ` +
+ `b2: 1, ` +
+ `b3: 5, ` +
+ `b4: (>=2 & <=3), ` +
+ `b5: (>=3 & <=5), ` +
+ `b6: 5, ` +
+ `b7: _|_(incompatible bounds >=6 and <=5), ` +
+ `b8: (>=1 & <=5), ` +
+ `b9: 1, ` +
+ `b10: 5, ` +
+ `b11: (>=2 & <=3), ` +
+ `b12: (>=3 & <=5), ` +
+ `b13: 5, ` +
+ `b14: _|_(incompatible bounds >=6 and <=5), ` +
+ `c1: (>=1 & <=5), ` +
+ `c2: (<=5 & >=1), ` +
+ `c3: _|_((string & >=1):unsupported op &((string)*, (number)*)), ` +
+ `c4: _|_(((>=1 & <=5) & string):unsupported op &((number)*, (string)*)), ` +
+ `s1: "e", ` +
+ `s2: "ee", ` +
+ `n1: (>=1 & <=2), ` +
+ `n2: _|_((int & >=1.1):unsupported op &((int)*, (float)*)), ` +
+ `n3: 2, ` +
+ `n4: 0.09999, ` +
+ `n5: 2.5}`,
}, {
desc: "predefined ranges",
in: `
@@ -799,7 +879,7 @@
e1: 100_000
`,
out: `<0>{k1: 44, k2: -8000000000, ` +
- `e1: _|_(((-32768..32767) & 100000):value 100000 not in range (-32768..32767))}`,
+ `e1: _|_((<=32767 & 100000):100000 not within bound <=32767)}`,
}, {
desc: "field comprehensions",
in: `
@@ -1070,18 +1150,18 @@
}, {
desc: "ips",
in: `
- IP: 4*[ 0..255 ]
+ IP: 4*[ uint8 ]
Private:
- *[ 192, 168, 0..255, 0..255 ] |
- [ 10, 0..255, 0..255, 0..255] |
- [ 172, 16..32, 0..255, 0..255 ]
+ *[ 192, 168, uint8, uint8 ] |
+ [ 10, uint8, uint8, uint8] |
+ [ 172, >=16 & <=32, uint8, uint8 ]
Inst: Private & [ _, 10, ... ]
MyIP: Inst & [_, _, 10, 10 ]
`,
- out: `<0>{IP: 4*[(0..255)], Private: [192,168,(0..255),(0..255)], Inst: [10,10,(0..255),(0..255)], MyIP: [10,10,10,10]}`,
+ out: `<0>{IP: 4*[(>=0 & <=255)], Private: [192,168,(>=0 & <=255),(>=0 & <=255)], Inst: [10,10,(>=0 & <=255),(>=0 & <=255)], MyIP: [10,10,10,10]}`,
}, {
desc: "complex interaction of groundness",
in: `
diff --git a/cue/rewrite.go b/cue/rewrite.go
index 6e28522..479605c 100644
--- a/cue/rewrite.go
+++ b/cue/rewrite.go
@@ -83,13 +83,12 @@
func (x *numLit) rewrite(ctx *context, fn rewriteFunc) value { return x }
func (x *durationLit) rewrite(ctx *context, fn rewriteFunc) value { return x }
-func (x *rangeLit) rewrite(ctx *context, fn rewriteFunc) value {
- from := rewrite(ctx, x.from, fn)
- to := rewrite(ctx, x.to, fn)
- if from == x.from && to == x.to {
+func (x *bound) rewrite(ctx *context, fn rewriteFunc) value {
+ v := rewrite(ctx, x.value, fn)
+ if v == x.value {
return x
}
- return &rangeLit{x.baseValue, from, to}
+ return &bound{x.baseValue, x.op, v}
}
func (x *interpolation) rewrite(ctx *context, fn rewriteFunc) value {
@@ -177,6 +176,11 @@
return &binaryExpr{x.baseValue, x.op, left, right}
}
+func (x *unification) rewrite(ctx *context, fn rewriteFunc) value {
+ // Can a unification ever be rewritten as it is a post-evaluation type?
+ panic("cue: unification only used post-evaluation")
+}
+
func (x *disjunction) rewrite(ctx *context, fn rewriteFunc) value {
values := make([]dValue, len(x.values))
changed := false
diff --git a/cue/scanner/scanner.go b/cue/scanner/scanner.go
index f5f0881..5b8fc65 100644
--- a/cue/scanner/scanner.go
+++ b/cue/scanner/scanner.go
@@ -730,7 +730,7 @@
s.next()
tok = token.ELLIPSIS
} else {
- tok = token.RANGE
+ s.error(s.file.Offset(pos), "illegal token '..'; expected '.'")
}
} else {
tok = token.PERIOD
diff --git a/cue/scanner/scanner_test.go b/cue/scanner/scanner_test.go
index 45e73a5..d32b057 100644
--- a/cue/scanner/scanner_test.go
+++ b/cue/scanner/scanner_test.go
@@ -134,7 +134,6 @@
{token.NEQ, "!=", operator},
{token.LEQ, "<=", operator},
{token.GEQ, ">=", operator},
- {token.RANGE, "..", operator},
{token.ELLIPSIS, "...", operator},
{token.LPAREN, "(", operator},
diff --git a/cue/subsume.go b/cue/subsume.go
index fccc07c..f4d9a18 100644
--- a/cue/subsume.go
+++ b/cue/subsume.go
@@ -62,6 +62,28 @@
// // no resolution if references are in play.
// return false, false
}
+ switch lt := lt.(type) {
+ case *unification:
+ if _, ok := gt.(*unification); !ok {
+ for _, x := range lt.values {
+ if subsumes(ctx, gt, x, mode) {
+ return true
+ }
+ }
+ return false
+ }
+
+ case *disjunction:
+ if _, ok := gt.(*disjunction); !ok {
+ for _, x := range lt.values {
+ if !subsumes(ctx, gt, x.val, mode) {
+ return false
+ }
+ }
+ return true
+ }
+ }
+
return gt.subsumesImpl(ctx, lt, mode)
}
@@ -97,38 +119,80 @@
return true
}
-func (x *rangeLit) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
- k := unifyType(x.from.kind(), x.to.kind())
- if k.isDone() && k.isGround() {
- switch y := v.(type) {
- case *rangeLit:
- // structural equivalence
- return subsumes(ctx, x.from, y.from, 0) && subsumes(ctx, x.to, y.to, 0)
- case *numLit, *stringLit, *durationLit:
- kv := v.kind()
- k, _ := matchBinOpKind(opAdd, k, kv)
- if k != kv {
+func (x *bound) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
+ if isBottom(v) {
+ return true
+ }
+ kx := x.value.kind()
+ if !kx.isDone() || !kx.isGround() {
+ return false
+ }
+
+ switch y := v.(type) {
+ case *bound:
+ if ky := y.value.kind(); ky.isDone() && ky.isGround() {
+ if (kx&ky)&^kx != 0 {
return false
}
- v := v.(evaluated)
- if x.from != nil {
- from := minNumRaw(x.from)
- if !from.kind().isGround() {
- return subsumes(ctx, from, v, 0) // false negative is okay
+ // x subsumes y if
+ // x: >= a, y: >= b ==> a <= b
+ // x: >= a, y: > b ==> a <= b
+ // x: > a, y: > b ==> a <= b
+ // x: > a, y: >= b ==> a < b
+ //
+ // x: <= a, y: <= b ==> a >= b
+ //
+ // x: != a, y: != b ==> a != b
+ //
+ // false if types or op direction doesn't match
+
+ xv := x.value.(evaluated)
+ yv := y.value.(evaluated)
+ switch x.op {
+ case opGtr:
+ if y.op == opGeq {
+ return test(ctx, x, opLss, xv, yv)
}
- return leq(ctx, x, x.from.(evaluated), v)
- }
- if x.to != nil {
- to := minNumRaw(x.to)
- if !to.kind().isGround() {
- return subsumes(ctx, to, v, 0) // false negative is okay
+ fallthrough
+ case opGeq:
+ if y.op == opGtr || y.op == opGeq {
+ return test(ctx, x, opLeq, xv, yv)
}
- return leq(ctx, x, v, x.to.(evaluated))
+ case opLss:
+ if y.op == opLeq {
+ return test(ctx, x, opGtr, xv, yv)
+ }
+ fallthrough
+ case opLeq:
+ if y.op == opLss || y.op == opLeq {
+ return test(ctx, x, opGeq, xv, yv)
+ }
+ case opNeq:
+ switch y.op {
+ case opNeq:
+ return test(ctx, x, opEql, xv, yv)
+ case opGeq:
+ return test(ctx, x, opLss, xv, yv)
+ case opGtr:
+ return test(ctx, x, opLeq, xv, yv)
+ case opLss:
+ return test(ctx, x, opGeq, xv, yv)
+ case opLeq:
+ return test(ctx, x, opGtr, xv, yv)
+ }
+
+ default:
+ // opNeq already handled above.
+ panic("cue: undefined bound mode")
}
- return true
}
+ // structural equivalence
+ return false
+
+ case *numLit, *stringLit, *durationLit, *boolLit:
+ return test(ctx, x, x.op, y.(evaluated), x.value.(evaluated))
}
- return isBottom(v)
+ return false
}
func (x *nullLit) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
@@ -207,12 +271,38 @@
return isBottom(v)
}
+func (x *unification) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
+ if y, ok := v.(*unification); ok {
+ // A unification subsumes another unification if for all values a in x
+ // there is a value b in y such that a subsumes b.
+ //
+ // This assumes overlapping ranges in disjunctions are merged.If this is
+ // not the case, subsumes will return a false negative, which is
+ // allowed.
+ outer:
+ for _, vx := range x.values {
+ for _, vy := range y.values {
+ if subsumes(ctx, vx, vy, mode) {
+ continue outer
+ }
+ }
+ return false
+ }
+ return true
+ }
+ subsumed := true
+ for _, vx := range x.values {
+ subsumed = subsumed && subsumes(ctx, vx, v, mode)
+ }
+ return subsumed
+}
+
// subsumes for disjunction is logically precise. However, just like with
// structural subsumption, it should not have to be called after evaluation.
func (x *disjunction) subsumesImpl(ctx *context, v value, mode subsumeMode) bool {
// A disjunction subsumes another disjunction if all values of v are
- // subsumed by the values of x, and default values in v are subsumed by the
- // default values of x.
+ // subsumed by any of the values of x, and default values in v are subsumed
+ // by the default values of x.
//
// This assumes that overlapping ranges in x are merged. If this is not the
// case, subsumes will return a false negative, which is allowed.
diff --git a/cue/subsume_test.go b/cue/subsume_test.go
index b48f8ef..021fd54 100644
--- a/cue/subsume_test.go
+++ b/cue/subsume_test.go
@@ -167,7 +167,7 @@
97: {subsumes: true, in: `a: number + number, b: int + int`},
// TODO: allow subsumption of unevaluated values?
// TODO: may be false if we allow arithmetic on incomplete values.
- 98: {subsumes: true, in: `a: int + int, b: int * int`},
+ 98: {subsumes: false, in: `a: int + int, b: int * int`},
99: {subsumes: true, in: `a: !int, b: !int`},
100: {subsumes: true, in: `a: !number, b: !int`},
@@ -175,8 +175,8 @@
// true because both evaluate to bottom
101: {subsumes: true, in: `a: !int, b: !number`},
// TODO: allow subsumption of unevaluated values?
- // true because both evaluate to bottom
- 102: {subsumes: true, in: `a: int + int, b: !number`},
+ // May be true because both evaluate to bottom. false is always allowed.
+ 102: {subsumes: false, in: `a: int + int, b: !number`},
// TODO: allow subsumption of unevaluated values?
// true because both evaluate to bool
103: {subsumes: true, in: `a: !bool, b: bool`},
@@ -256,7 +256,52 @@
150: {subsumes: false, in: `a: number | *1, b: number | *2`},
151: {subsumes: true, in: `a: number | *2, b: number | *2`},
152: {subsumes: true, in: `a: int | *float, b: int | *2.0`},
- 154: {subsumes: true, in: `a: number, b: number | *2`},
+ 153: {subsumes: true, in: `a: int | *2, b: int | *2.0`},
+ 154: {subsumes: true, in: `a: number | *2 | *3, b: number | *2`},
+ 155: {subsumes: true, in: `a: number, b: number | *2`},
+
+ // Bounds
+ 170: {subsumes: true, in: `a: >=2, b: >=2`},
+ 171: {subsumes: true, in: `a: >=1, b: >=2`},
+ 172: {subsumes: true, in: `a: >0, b: >=2`},
+ 173: {subsumes: true, in: `a: >1, b: >1`},
+ 174: {subsumes: true, in: `a: >=1, b: >1`},
+ 175: {subsumes: false, in: `a: >1, b: >=1`},
+ 176: {subsumes: true, in: `a: >=1, b: >=1`},
+ 177: {subsumes: true, in: `a: <1, b: <1`},
+ 178: {subsumes: true, in: `a: <=1, b: <1`},
+ 179: {subsumes: false, in: `a: <1, b: <=1`},
+ 180: {subsumes: true, in: `a: <=1, b: <=1`},
+
+ 181: {subsumes: true, in: `a: !=1, b: !=1`},
+ 182: {subsumes: false, in: `a: !=1, b: !=2`},
+
+ 183: {subsumes: false, in: `a: !=1, b: <=1`},
+ 184: {subsumes: true, in: `a: !=1, b: <1`},
+ 185: {subsumes: false, in: `a: !=1, b: >=1`},
+ 186: {subsumes: true, in: `a: !=1, b: <1`},
+
+ 187: {subsumes: true, in: `a: !=1, b: <=0`},
+ 188: {subsumes: true, in: `a: !=1, b: >=2`},
+ 189: {subsumes: true, in: `a: !=1, b: >1`},
+
+ 195: {subsumes: false, in: `a: >=2, b: !=2`},
+ 196: {subsumes: false, in: `a: >2, b: !=2`},
+ 197: {subsumes: false, in: `a: <2, b: !=2`},
+ 198: {subsumes: false, in: `a: <=2, b: !=2`},
+
+ // Conjunctions
+ 200: {subsumes: true, in: `a: >0, b: >=2 & <=100`},
+ 201: {subsumes: false, in: `a: >0, b: >=0 & <=100`},
+
+ 210: {subsumes: true, in: `a: >=0 & <=100, b: 10`},
+ 211: {subsumes: true, in: `a: >=0 & <=100, b: >=0 & <=100`},
+ 212: {subsumes: false, in: `a: !=2 & !=4, b: >3`},
+ 213: {subsumes: true, in: `a: !=2 & !=4, b: >5`},
+
+ // Disjunctions
+ 230: {subsumes: true, in: `a: >5, b: >10 | 8`},
+ 231: {subsumes: false, in: `a: >8, b: >10 | 8`},
}
re := regexp.MustCompile(`a: (.*).*b: ([^\n]*)`)
diff --git a/cue/token/token.go b/cue/token/token.go
index edc9050..5238bc9 100644
--- a/cue/token/token.go
+++ b/cue/token/token.go
@@ -77,7 +77,6 @@
LBRACE // {
COMMA // ,
PERIOD // .
- RANGE // ..
ELLIPSIS // ...
RPAREN // )
@@ -148,7 +147,6 @@
LBRACE: "{",
COMMA: ",",
PERIOD: ".",
- RANGE: "..",
ELLIPSIS: "...",
RPAREN: ")",
@@ -222,8 +220,6 @@
return 6
case MUL, QUO, REM, IDIV, IMOD, IQUO, IREM:
return 7
- case RANGE:
- return 8
}
return lowestPrec
}
diff --git a/cue/types.go b/cue/types.go
index 8f669e3..7cecd8c 100644
--- a/cue/types.go
+++ b/cue/types.go
@@ -494,8 +494,8 @@
}
// Kind returns the kind of value. It returns BottomKind for atomic values that
-// are not concrete. For instance, it will return BottomKind for the range
-// 0..5.
+// are not concrete. For instance, it will return BottomKind for the bounds
+// >=0.
func (v Value) Kind() Kind {
k := v.eval(v.ctx()).kind()
if k.isGround() {
diff --git a/cue/types_test.go b/cue/types_test.go
index fca29ae..4f43796 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -107,7 +107,7 @@
kind: NumberKind,
incompleteKind: NumberKind,
}, {
- value: `0..5`,
+ value: `>=0 & <5`,
kind: BottomKind,
incompleteKind: NumberKind,
}, {
@@ -513,7 +513,7 @@
value: `[1,2,3]`,
res: "[1,2,3,]",
}, {
- value: `(0..5)*[1,2,3, ...int]`,
+ value: `>=5*[1,2,3, ...int]`,
res: "[1,2,3,]",
}, {
value: `[x for x in y if x > 1]
@@ -998,7 +998,7 @@
value: `[int]`,
err: `cannot convert incomplete value`,
}, {
- value: `((0..3) * [1, 2])`,
+ value: `(>=3 * [1, 2])`,
json: `[1,2]`,
}, {
value: `{}`,
@@ -1071,7 +1071,7 @@
value: `[int]`,
out: `[int]`,
}, {
- value: `((0..3) * [1, 2])`,
+ value: `(>=3 * [1, 2])`,
out: `[1,2]`,
}, {
value: `{}`,
diff --git a/cue/value.go b/cue/value.go
index 945fe30..897af40 100644
--- a/cue/value.go
+++ b/cue/value.go
@@ -354,6 +354,8 @@
var ten = big.NewInt(10)
+var one = parseInt(intKind, "1")
+
func (x *numLit) kind() kind { return x.k }
func (x *numLit) strValue() string { return x.v.String() }
@@ -404,26 +406,30 @@
func (x *durationLit) kind() kind { return durationKind }
func (x *durationLit) strValue() string { return x.d.String() }
-type rangeLit struct {
+type bound struct {
baseValue
- from, to value
+ op op // opNeq, opLss, opLeq, opGeq, or opGtr
+ value value
}
-func (x *rangeLit) kind() kind {
- return unifyType(x.from.kind(), x.to.kind()) | nonGround
+func (x *bound) kind() kind {
+ k := x.value.kind()
+ if x.op == opNeq && k&atomKind == nullKind {
+ k = typeKinds &^ nullKind
+ }
+ return k | nonGround
}
-func mkIntRange(a, b string) *rangeLit {
- from := parseInt(intKind, a)
- to := parseInt(intKind, b)
- return &rangeLit{
- binSrc(token.NoPos, opRange, from, to),
- from,
- to,
+func mkIntRange(a, b string) evaluated {
+ from := &bound{op: opGeq, value: parseInt(intKind, a)}
+ to := &bound{op: opLeq, value: parseInt(intKind, b)}
+ return &unification{
+ binSrc(token.NoPos, opUnify, from, to),
+ []evaluated{from, to},
}
}
-var predefinedRanges = map[string]*rangeLit{
+var predefinedRanges = map[string]evaluated{
"rune": mkIntRange("0", strconv.Itoa(0x10FFFF)),
"int8": mkIntRange("-128", "127"),
"int16": mkIntRange("-32768", "32767"),
@@ -435,6 +441,7 @@
// Do not include an alias for "byte", as it would be too easily confused
// with the builtin "bytes".
+ "uint": &bound{op: opGeq, value: parseInt(intKind, "0")},
"uint8": mkIntRange("0", "255"),
"uint16": mkIntRange("0", "65535"),
"uint32": mkIntRange("0", "4294967295"),
@@ -514,7 +521,7 @@
// lo and hi must be nil or a ground integer.
func (x *list) slice(ctx *context, lo, hi *numLit) evaluated {
a := x.a
- max := maxNum(x.len.(evaluated))
+ max := maxNum(x.len).evalPartial(ctx)
if hi != nil {
n := hi.intValue(ctx)
if n < 0 {
@@ -956,8 +963,22 @@
return kind | nonGround
}
-// TODO: make disjunction a binOp, but translate disjunctions into
-// arrays, or at least linked lists.
+// unification collects evaluated values that are not mutually exclusive
+// but cannot be represented as a single value. It allows doing the bookkeeping
+// on accumulating conjunctions, simplifying them along the way, until they do
+// resolve into a single value.
+type unification struct {
+ baseValue
+ values []evaluated
+}
+
+func (x *unification) kind() kind {
+ k := topKind
+ for _, v := range x.values {
+ k &= v.kind()
+ }
+ return k | nonGround
+}
type disjunction struct {
baseValue