cue: simplify bounds on export
Change-Id: I915b4e833d528bc3eef4ce834eb42e2d6fed4b72
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2175
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/export.go b/cue/export.go
index 94bcfbc..343edcc 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -23,6 +23,8 @@
"unicode"
"unicode/utf8"
+ "github.com/cockroachdb/apd"
+
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/token"
)
@@ -300,14 +302,18 @@
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])
+ b := boundSimplifier{p: p}
+ vals := make([]evaluated, 0, 3)
+ for _, v := range x.values {
+ if !b.add(v) {
+ vals = append(vals, v)
+ }
}
- bin := p.expr(x.values[0])
- for _, v := range x.values[1:] {
- bin = &ast.BinaryExpr{X: bin, Op: token.AND, Y: p.expr(v)}
+ e := b.expr(p.ctx)
+ for _, v := range vals {
+ e = wrapBin(e, p.expr(v), opUnify)
}
- return bin
+ return e
case *disjunction:
if len(x.values) == 1 {
@@ -684,3 +690,198 @@
}
return buf
}
+
+type boundSimplifier struct {
+ p *exporter
+
+ isInt bool
+ min *bound
+ minNum *numLit
+ max *bound
+ maxNum *numLit
+}
+
+func (s *boundSimplifier) add(v value) (used bool) {
+ switch x := v.(type) {
+ case *basicType:
+ switch x.k & scalarKinds {
+ case intKind:
+ s.isInt = true
+ return true
+ }
+
+ case *bound:
+ if x.k&concreteKind == intKind {
+ s.isInt = true
+ }
+ switch x.op {
+ case opGtr:
+ if n, ok := x.value.(*numLit); ok {
+ if s.min == nil || s.minNum.v.Cmp(&n.v) != 1 {
+ s.min = x
+ s.minNum = n
+ }
+ return true
+ }
+
+ case opGeq:
+ if n, ok := x.value.(*numLit); ok {
+ if s.min == nil || s.minNum.v.Cmp(&n.v) == -1 {
+ s.min = x
+ s.minNum = n
+ }
+ return true
+ }
+
+ case opLss:
+ if n, ok := x.value.(*numLit); ok {
+ if s.max == nil || s.maxNum.v.Cmp(&n.v) != -1 {
+ s.max = x
+ s.maxNum = n
+ }
+ return true
+ }
+
+ case opLeq:
+ if n, ok := x.value.(*numLit); ok {
+ if s.max == nil || s.maxNum.v.Cmp(&n.v) == 1 {
+ s.max = x
+ s.maxNum = n
+ }
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+type builtinRange struct {
+ typ string
+ lo *apd.Decimal
+ hi *apd.Decimal
+}
+
+func makeDec(s string) *apd.Decimal {
+ d, _, err := apd.NewFromString(s)
+ if err != nil {
+ panic(err)
+ }
+ return d
+}
+
+func (s *boundSimplifier) expr(ctx *context) (e ast.Expr) {
+ if s.min == nil || s.max == nil {
+ return nil
+ }
+ switch {
+ case s.isInt:
+ t := s.matchRange(intRanges)
+ if t != "" {
+ e = ast.NewIdent(t)
+ break
+ }
+ if sign := s.minNum.v.Sign(); sign == -1 {
+ e = ast.NewIdent("int")
+
+ } else {
+ e = ast.NewIdent("uint")
+ if sign == 0 && s.min.op == opGeq {
+ s.min = nil
+ break
+ }
+ }
+ fallthrough
+ default:
+ t := s.matchRange(floatRanges)
+ if t != "" {
+ e = wrapBin(e, ast.NewIdent(t), opUnify)
+ }
+ }
+
+ if s.min != nil {
+ e = wrapBin(e, s.p.expr(s.min), opUnify)
+ }
+ if s.max != nil {
+ e = wrapBin(e, s.p.expr(s.max), opUnify)
+ }
+ return e
+}
+
+func (s *boundSimplifier) matchRange(ranges []builtinRange) (t string) {
+ for _, r := range ranges {
+ if !s.minNum.v.IsZero() && s.min.op == opGeq && s.minNum.v.Cmp(r.lo) == 0 {
+ switch s.maxNum.v.Cmp(r.hi) {
+ case 0:
+ if s.max.op == opLeq {
+ s.max = nil
+ }
+ s.min = nil
+ return r.typ
+ case -1:
+ if !s.minNum.v.IsZero() {
+ s.min = nil
+ return r.typ
+ }
+ case 1:
+ }
+ } else if s.max.op == opLeq && s.maxNum.v.Cmp(r.hi) == 0 {
+ switch s.minNum.v.Cmp(r.lo) {
+ case -1:
+ case 0:
+ if s.min.op == opGeq {
+ s.min = nil
+ }
+ fallthrough
+ case 1:
+ s.max = nil
+ t = r.typ
+ return r.typ
+ }
+ }
+ }
+ return ""
+}
+
+var intRanges = []builtinRange{
+ {"int8", makeDec("-128"), makeDec("127")},
+ {"int16", makeDec("-32768"), makeDec("32767")},
+ {"int32", makeDec("-2147483648"), makeDec("2147483647")},
+ {"int64", makeDec("-9223372036854775808"), makeDec("9223372036854775807")},
+ {"int128", makeDec("-170141183460469231731687303715884105728"),
+ makeDec("170141183460469231731687303715884105727")},
+
+ {"uint8", makeDec("0"), makeDec("255")},
+ {"uint16", makeDec("0"), makeDec("65535")},
+ {"uint32", makeDec("0"), makeDec("4294967295")},
+ {"uint64", makeDec("0"), makeDec("18446744073709551615")},
+ {"uint128", makeDec("0"), makeDec("340282366920938463463374607431768211455")},
+
+ // {"rune", makeDec("0"), makeDec(strconv.Itoa(0x10FFFF))},
+}
+
+var floatRanges = []builtinRange{
+ // 2**127 * (2**24 - 1) / 2**23
+ {"float32",
+ makeDec("-3.40282346638528859811704183484516925440e+38"),
+ makeDec("+3.40282346638528859811704183484516925440e+38")},
+
+ // 2**1023 * (2**53 - 1) / 2**52
+ {"float64",
+ makeDec("-1.797693134862315708145274237317043567981e+308"),
+ makeDec("+1.797693134862315708145274237317043567981e+308")},
+}
+
+func wrapBin(a, b ast.Expr, op op) ast.Expr {
+ if a == nil {
+ return b
+ }
+ if b == nil {
+ return a
+ }
+ return &ast.BinaryExpr{
+ X: a,
+ Op: opMap[op],
+ Y: b,
+ }
+}
diff --git a/cue/export_test.go b/cue/export_test.go
index 45659a2..464c56f 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -165,6 +165,40 @@
b: [1 | 2]
}`),
}, {
+ raw: true,
+ eval: true,
+ in: `{
+ u16: int & >=0 & <=65535
+ u32: uint32
+ u64: uint64
+ u128: uint128
+ u8: uint8
+ ua: uint16 & >0
+ us: >=0 & <10_000 & int
+ i16: >=-32768 & int & <=32767
+ i32: int32 & > 0
+ i64: int64
+ i128: int128
+ f64: float64
+ fi: float64 & int
+ }`,
+ out: unindent(`
+ {
+ u16: uint16
+ u32: uint32
+ u64: uint64
+ u128: uint128
+ u8: uint8
+ ua: uint16 & >0
+ us: uint & <10000
+ i16: int16
+ i32: int32 & >0
+ i64: int64
+ i128: int128
+ f64: float64
+ fi: int & float64
+ }`),
+ }, {
raw: true,
in: `{ a: [1, 2], b: { "\(k)": v for k, v in a if v > 1 } }`,
out: unindent(`