blob: a581a278991e6999ed135cf17e62a41fbc568892 [file] [log] [blame]
// Copyright 2018 The 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 cue
import (
"fmt"
"math/rand"
"sort"
"strconv"
"strings"
"unicode/utf8"
"github.com/cockroachdb/apd/v2"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/token"
"cuelang.org/go/internal"
)
func doEval(m options) bool {
return !m.raw
}
func exportFile(ctx *context, inst *Instance, v value, m options) (f *ast.File, imports []string) {
e := exporter{ctx, m, nil, map[label]bool{}, map[string]importInfo{}, false, nil}
top, ok := v.evalPartial(ctx).(*structLit)
if ok {
top, err := top.expandFields(ctx)
if err != nil {
v = err
} else {
for _, a := range top.Arcs {
e.top[a.Label] = true
}
}
}
value := e.expr(v)
return e.toFile(inst, value, m)
}
func export(ctx *context, inst *Instance, v value, m options) (n ast.Node, imports []string) {
e := exporter{ctx, m, nil, map[label]bool{}, map[string]importInfo{}, false, nil}
top, ok := v.evalPartial(ctx).(*structLit)
if ok {
top, err := top.expandFields(ctx)
if err != nil {
v = err
} else {
for _, a := range top.Arcs {
e.top[a.Label] = true
}
}
}
value := e.expr(v)
if len(e.imports) == 0 && inst == nil {
// TODO: unwrap structs?
return value, nil
}
return e.toFile(inst, value, m)
}
func (e *exporter) toFile(inst *Instance, value ast.Expr, m options) (n *ast.File, imports []string) {
file := &ast.File{}
if inst != nil {
if inst.PkgName != "" {
p := &ast.Package{Name: ast.NewIdent(inst.PkgName)}
file.Decls = append(file.Decls, p)
if m.docs {
for _, d := range inst.Doc() {
p.AddComment(d)
break
}
}
}
}
imports = make([]string, 0, len(e.imports))
for k := range e.imports {
imports = append(imports, k)
}
sort.Strings(imports)
if len(imports) > 0 {
importDecl := &ast.ImportDecl{}
file.Decls = append(file.Decls, importDecl)
for _, k := range imports {
info := e.imports[k]
ident := (*ast.Ident)(nil)
if info.name != "" {
ident = ast.NewIdent(info.name)
}
if info.alias != "" {
file.Decls = append(file.Decls, &ast.LetClause{
Ident: ast.NewIdent(info.alias),
Expr: ast.NewIdent(info.short),
})
}
importDecl.Specs = append(importDecl.Specs, ast.NewImport(ident, k))
}
}
if obj, ok := value.(*ast.StructLit); ok {
file.Decls = append(file.Decls, obj.Elts...)
} else {
file.Decls = append(file.Decls, &ast.EmbedDecl{Expr: value})
}
// resolve the file.
return file, imports
}
type exporter struct {
ctx *context
mode options
stack []remap
top map[label]bool // label to alias or ""
imports map[string]importInfo // pkg path to info
inDef bool // TODO(recclose):use count instead
incomplete []source
}
func (p *exporter) addIncomplete(v value) {
// TODO: process incomplete values
}
type importInfo struct {
name string
short string
alias string
}
type remap struct {
key scope // structLit or params
from label
to *ast.Ident
syn *ast.StructLit
}
func (p *exporter) unique(s string) string {
s = strings.ToUpper(s)
lab := s
for {
if !p.ctx.HasLabel(lab) {
p.ctx.Label(lab, true)
break
}
lab = s + fmt.Sprintf("%0.6x", rand.Intn(1<<24))
}
return lab
}
func (p *exporter) label(f label) ast.Label {
str := p.ctx.LabelStr(f)
if strings.HasPrefix(str, "#") && !f.IsDef() ||
strings.HasPrefix(str, "_") && !f.IsHidden() ||
!ast.IsValidIdent(str) {
return ast.NewLit(token.STRING, strconv.Quote(str))
}
return &ast.Ident{Name: str}
}
func (p *exporter) identifier(f label) *ast.Ident {
str := p.ctx.LabelStr(f)
return &ast.Ident{Name: str}
}
func (p *exporter) ident(str string) *ast.Ident {
return &ast.Ident{Name: str}
}
func (p *exporter) clause(v value) (n ast.Clause, next yielder) {
switch x := v.(type) {
case *feed:
feed := &ast.ForClause{
Value: p.identifier(x.fn.params.arcs[1].Label),
Source: p.expr(x.Src),
}
key := x.fn.params.arcs[0]
if p.ctx.LabelStr(key.Label) != "_" {
feed.Key = p.identifier(key.Label)
}
return feed, x.fn.value.(yielder)
case *guard:
return &ast.IfClause{Condition: p.expr(x.Condition)}, x.Dst
}
panic(fmt.Sprintf("unsupported clause type %T", v))
}
func (p *exporter) shortName(inst *Instance, preferred, pkg string) string {
info, ok := p.imports[pkg]
short := info.short
if !ok {
short = inst.PkgName
if _, ok := p.top[p.ctx.Label(short, true)]; ok && preferred != "" {
short = preferred
info.name = short
}
for {
if _, ok := p.top[p.ctx.Label(short, true)]; !ok {
break
}
short += "x"
info.name = short
}
info.short = short
p.top[p.ctx.Label(short, true)] = true
p.imports[pkg] = info
}
f := p.ctx.Label(short, true)
for _, e := range p.stack {
if e.from == f {
if info.alias == "" {
info.alias = p.unique(short)
p.imports[pkg] = info
}
short = info.alias
break
}
}
return short
}
func (p *exporter) mkTemplate(v value, n *ast.Ident) ast.Label {
var expr ast.Expr
if v != nil {
expr = p.expr(v)
} else {
expr = ast.NewIdent("string")
}
switch n.Name {
case "", "_":
default:
expr = &ast.Alias{Ident: n, Expr: ast.NewIdent("string")}
}
return ast.NewList(expr)
}
func hasTemplate(s *ast.StructLit) bool {
for _, e := range s.Elts {
switch f := e.(type) {
case *ast.Ellipsis:
return true
case *ast.EmbedDecl:
if st, ok := f.Expr.(*ast.StructLit); ok && hasTemplate(st) {
return true
}
case *ast.Field:
label := f.Label
if _, ok := label.(*ast.TemplateLabel); ok {
return true
}
if a, ok := label.(*ast.Alias); ok {
label, ok = a.Expr.(ast.Label)
if !ok {
return false
}
}
if l, ok := label.(*ast.ListLit); ok {
if len(l.Elts) != 1 {
return false
}
expr := l.Elts[0]
if a, ok := expr.(*ast.Alias); ok {
expr = a.Expr
}
if i, ok := expr.(*ast.Ident); ok {
if i.Name == "_" || i.Name == "string" {
return true
}
}
}
}
}
return false
}
func (p *exporter) showOptional() bool {
return !p.mode.omitOptional && !p.mode.concrete
}
func (p *exporter) closeOrOpen(s *ast.StructLit, isClosed bool) ast.Expr {
// Note, there is no point in printing close if we are dropping optional
// fields, as by this the meaning of close will change anyway.
if !p.showOptional() || p.mode.final {
return s
}
if isClosed && !p.inDef && !hasTemplate(s) {
return ast.NewCall(ast.NewIdent("close"), s)
}
if !isClosed && p.inDef && !hasTemplate(s) {
s.Elts = append(s.Elts, &ast.Ellipsis{})
}
return s
}
func (p *exporter) isComplete(v value, all bool) bool {
switch x := v.(type) {
case *numLit, *stringLit, *bytesLit, *nullLit, *boolLit:
return true
case *list:
if p.mode.final || !all {
return true
}
if x.isOpen() {
return false
}
for i := range x.elem.Arcs {
if !p.isComplete(x.at(p.ctx, i), all) {
return false
}
}
return true
case *structLit:
return !all && p.mode.final
case *bottom:
return !isIncomplete(x)
case *closeIfStruct:
return p.isComplete(x.value, all)
}
return false
}
func isDisjunction(v value) bool {
switch x := v.(type) {
case *disjunction:
return true
case *closeIfStruct:
return isDisjunction(x.value)
}
return false
}
func (p *exporter) recExpr(v value, e evaluated, optional bool) ast.Expr {
var m evaluated
if !p.mode.final {
m = e.evalPartial(p.ctx)
} else {
m = p.ctx.manifest(e)
}
isComplete := p.isComplete(m, false)
if optional || (!isComplete && !p.mode.concrete) {
if !p.mode.final {
// Schema mode.
// Print references as they are, if applicable.
//
// TODO: We probably should not allow resolving references in
// schema mode, or at most allow resolving _some_ references, like
// those defined outside of packages.
noResolve := !p.mode.resolveReferences
if optional {
// Don't resolve references when a field is optional.
// This may break some unnecessary cycles.
noResolve = true
}
if isBottom(e) || (v.Kind().hasReferences() && noResolve) {
return p.expr(v)
}
} else {
// Data mode.
if p.mode.concrete && !m.Kind().isGround() {
p.addIncomplete(v)
}
// TODO: do something more principled than this hack.
// This likely requires disjunctions to keep track of original
// values (so using arcs instead of values).
opts := options{concrete: true, raw: true}
p := &exporter{p.ctx, opts, p.stack, p.top, p.imports, p.inDef, nil}
if isDisjunction(v) || isBottom(e) {
return p.expr(v)
}
if v.Kind()&structKind == 0 {
return p.expr(e)
}
if optional || isDisjunction(e) {
// Break cycles: final and resolveReferences really should not be
// used with optional.
p.mode.resolveReferences = false
p.mode.final = false
return p.expr(v)
}
}
}
return p.expr(e)
}
func (p *exporter) isClosed(x *structLit) bool {
return x.closeStatus.shouldClose()
}
func (p *exporter) badf(msg string, args ...interface{}) ast.Expr {
msg = fmt.Sprintf(msg, args...)
bad := &ast.BadExpr{}
bad.AddComment(&ast.CommentGroup{
Doc: true,
List: []*ast.Comment{{Text: "// " + msg}},
})
return bad
}
func (p *exporter) expr(v value) ast.Expr {
// TODO: use the raw expression for convert incomplete errors downstream
// as well.
if doEval(p.mode) || p.mode.concrete {
e := v.evalPartial(p.ctx)
x := e
if p.mode.final {
x = p.ctx.manifest(e)
}
if !p.isComplete(x, true) {
if p.mode.concrete && !x.Kind().isGround() {
p.addIncomplete(v)
}
switch {
case isBottom(e):
if p.mode.concrete {
p.addIncomplete(v)
}
p = &exporter{p.ctx, options{raw: true}, p.stack, p.top, p.imports, p.inDef, nil}
return p.expr(v)
case v.Kind().hasReferences() && !p.mode.resolveReferences:
case doEval(p.mode):
v = e
}
} else {
v = x
}
}
old := p.stack
defer func() { p.stack = old }()
// TODO: also add position information.
switch x := v.(type) {
case *builtin:
if x.pkg == 0 {
return ast.NewIdent(x.Name)
}
pkg := p.ctx.LabelStr(x.pkg)
inst := builtins[pkg]
short := p.shortName(inst, "", pkg)
return ast.NewSel(ast.NewIdent(short), x.Name)
case *nodeRef:
if x.label == 0 {
// NOTE: this nodeRef is used within a selector.
return nil
}
short := p.ctx.LabelStr(x.label)
if inst := p.ctx.getImportFromNode(x.node); inst != nil {
return ast.NewIdent(p.shortName(inst, short, inst.ImportPath))
}
// fix shadowed label.
return ast.NewIdent(short)
case *selectorExpr:
n := p.expr(x.X)
if n != nil {
return ast.NewSel(n, p.ctx.LabelStr(x.Sel))
}
f := x.Sel
ident := p.identifier(f)
node, ok := x.X.(*nodeRef)
if !ok {
return p.badf("selector without node")
}
if l, ok := node.node.(*lambdaExpr); ok && len(l.arcs) == 1 {
f = l.params.arcs[0].Label
// TODO: ensure it is shadowed.
ident = p.identifier(f)
return ident
}
// TODO: nodes may have been shadowed. Use different algorithm.
conflict := false
for i := len(p.stack) - 1; i >= 0; i-- {
e := &p.stack[i]
if e.from != f {
continue
}
if e.key != node.node {
conflict = true
continue
}
if conflict {
ident = e.to
if e.to == nil {
name := p.unique(p.ctx.LabelStr(f))
e.syn.Elts = append(e.syn.Elts, &ast.Alias{
Ident: p.ident(name),
Expr: p.identifier(f),
})
ident = p.ident(name)
e.to = ident
}
}
break
}
return ident
case *indexExpr:
return &ast.IndexExpr{X: p.expr(x.X), Index: p.expr(x.Index)}
case *sliceExpr:
return &ast.SliceExpr{
X: p.expr(x.X),
Low: p.expr(x.Lo),
High: p.expr(x.Hi),
}
case *callExpr:
call := &ast.CallExpr{}
b := x.Fun.evalPartial(p.ctx)
if b, ok := b.(*builtin); ok {
call.Fun = p.expr(b)
} else {
call.Fun = p.expr(x.Fun)
}
for _, a := range x.Args {
call.Args = append(call.Args, p.expr(a))
}
return call
case *customValidator:
call := ast.NewCall(p.expr(x.Builtin))
for _, a := range x.Args {
call.Args = append(call.Args, p.expr(a))
}
return call
case *unaryExpr:
return &ast.UnaryExpr{Op: opMap[x.Op], X: p.expr(x.X)}
case *binaryExpr:
// opUnifyUnchecked: represented as embedding. The two arguments must
// be structs.
if x.Op == opUnifyUnchecked {
s := ast.NewStruct()
return p.closeOrOpen(s, p.embedding(s, x))
}
return ast.NewBinExpr(opMap[x.Op], p.expr(x.X), p.expr(x.Y))
case *bound:
return &ast.UnaryExpr{Op: opMap[x.Op], X: p.expr(x.Expr)}
case *unification:
b := boundSimplifier{p: p}
vals := make([]evaluated, 0, 3)
for _, v := range x.Values {
if !b.add(v) {
vals = append(vals, v)
}
}
e := b.expr(p.ctx)
for _, v := range vals {
e = wrapBin(e, p.expr(v), opUnify)
}
return e
case *disjunction:
if len(x.Values) == 1 {
return p.expr(x.Values[0].Val)
}
expr := func(v dValue) ast.Expr {
e := p.expr(v.Val)
if v.Default {
e = &ast.UnaryExpr{Op: token.MUL, X: e}
}
return e
}
bin := expr(x.Values[0])
for _, v := range x.Values[1:] {
bin = ast.NewBinExpr(token.OR, bin, expr(v))
}
return bin
case *closeIfStruct:
return p.expr(x.value)
case *structLit:
st, err := p.structure(x, !p.isClosed(x))
if err != nil {
return p.expr(err)
}
expr := p.closeOrOpen(st, p.isClosed(x))
switch {
// If a template is non-nil, we only show it if printing of
// optional fields is requested. If a struct is not closed it was
// already generated before. Furthermore, if if we are in evaluation
// mode, the struct is already unified, so there is no need to print it.
case p.showOptional() && p.isClosed(x) && !doEval(p.mode):
if x.optionals == nil {
break
}
p.optionals(len(x.Arcs) > 0, st, x.optionals)
}
return expr
case *fieldComprehension:
panic("should be handled in structLit")
case *listComprehension:
var clauses []ast.Clause
for y, next := p.clause(x.clauses); ; y, next = p.clause(next) {
clauses = append(clauses, y)
if yield, ok := next.(*yield); ok {
return &ast.ListComprehension{
Expr: p.expr(yield.value),
Clauses: clauses,
}
}
}
case *nullLit:
return ast.NewNull()
case *boolLit:
return ast.NewBool(x.B)
case *stringLit:
return &ast.BasicLit{
Kind: token.STRING,
Value: quote(x.Str, '"'),
}
case *bytesLit:
return &ast.BasicLit{
Kind: token.STRING,
Value: quote(string(x.B), '\''),
}
case *numLit:
kind := token.FLOAT
if x.K&intKind != 0 {
kind = token.INT
}
return &ast.BasicLit{Kind: kind, Value: x.String()}
case *durationLit:
panic("unimplemented")
case *interpolation:
t := &ast.Interpolation{}
multiline := false
// TODO: mark formatting in interpolation itself.
for i := 0; i < len(x.Parts); i += 2 {
str := x.Parts[i].(*stringLit).Str
if strings.IndexByte(str, '\n') >= 0 {
multiline = true
break
}
}
quote := `"`
if multiline {
quote = `"""`
}
prefix := quote
suffix := `\(`
for i, e := range x.Parts {
if i%2 == 1 {
t.Elts = append(t.Elts, p.expr(e))
} else {
buf := []byte(prefix)
if i == len(x.Parts)-1 {
suffix = quote
}
str := e.(*stringLit).Str
if multiline {
buf = appendEscapeMulti(buf, str, '"')
} else {
buf = appendEscaped(buf, str, '"', true)
}
buf = append(buf, suffix...)
t.Elts = append(t.Elts, &ast.BasicLit{
Kind: token.STRING,
Value: string(buf),
})
}
prefix = ")"
}
return t
case *list:
list := &ast.ListLit{}
var expr ast.Expr = list
for i, a := range x.elem.Arcs {
if !doEval(p.mode) {
list.Elts = append(list.Elts, p.expr(a.v))
} else {
e := x.elem.at(p.ctx, i)
list.Elts = append(list.Elts, p.recExpr(a.v, e, false))
}
}
max := maxNum(x.len)
num, ok := max.(*numLit)
if !ok {
min := minNum(x.len)
num, _ = min.(*numLit)
}
ln := 0
if num != nil {
x, _ := num.X.Int64()
ln = int(x)
}
open := false
switch max.(type) {
case *top, *basicType:
open = true
}
if !ok || ln > len(x.elem.Arcs) {
list.Elts = append(list.Elts, &ast.Ellipsis{Type: p.expr(x.typ)})
if !open && !isTop(x.typ) {
expr = ast.NewBinExpr(
token.AND,
ast.NewBinExpr(
token.MUL,
p.expr(x.len),
ast.NewList(p.expr(x.typ))),
list,
)
}
}
return expr
case *bottom:
err := &ast.BottomLit{}
if x.format != "" {
msg := x.msg()
if len(x.sub) > 0 {
buf := strings.Builder{}
for i, b := range x.sub {
if i > 0 {
buf.WriteString("; ")
buf.WriteString(b.msg())
}
}
msg = buf.String()
}
comment := &ast.Comment{Text: "// " + msg}
err.AddComment(&ast.CommentGroup{
Line: true,
Position: 2,
List: []*ast.Comment{comment},
})
}
return err
case *top:
return p.ident("_")
case *basicType:
return p.ident(x.K.String())
case *lambdaExpr:
return p.ident("TODO: LAMBDA")
default:
panic(fmt.Sprintf("unimplemented type %T", x))
}
}
func (p *exporter) optionalsExpr(x *optionals, isClosed bool) ast.Expr {
st := ast.NewStruct()
// An empty struct has meaning in case of closed structs, where they
// indicate no other fields may be added. Non-closed empty structs should
// have been optimized away. In case they are not, it is just a no-op.
if x != nil {
p.optionals(false, st, x)
}
if isClosed {
return ast.NewCall(ast.NewIdent("close"), st)
}
return st
}
func (p *exporter) optionals(wrap bool, st *ast.StructLit, x *optionals) (skippedEllipsis bool) {
wrap = wrap || len(x.fields) > 1
switch x.op {
default:
for _, t := range x.fields {
l, ok := t.value.evalPartial(p.ctx).(*lambdaExpr)
if !ok {
// Really should not happen.
continue
}
v := l.value
if c, ok := v.(*closeIfStruct); ok {
v = c.value
}
f := &ast.Field{
Label: p.mkTemplate(t.key, p.identifier(l.params.arcs[0].Label)),
Value: p.expr(l.value),
}
if internal.IsEllipsis(f) {
skippedEllipsis = true
continue
}
if !wrap {
st.Elts = append(st.Elts, f)
continue
}
st.Elts = append(st.Elts, internal.EmbedStruct(ast.NewStruct(f)))
}
case opUnify:
// Optional constraints added with normal unification are embedded as an
// expression. This relies on the fact that a struct embedding a closed
// struct will itself be closed.
st.Elts = append(st.Elts, &ast.EmbedDecl{Expr: &ast.BinaryExpr{
X: p.optionalsExpr(x.left, x.left.isClosed()),
Op: token.AND,
Y: p.optionalsExpr(x.right, x.right.isClosed()),
}})
case opUnifyUnchecked:
// Constraints added with unchecked unification are embedded
// individually. It doesn't matter here whether this originated from
// regular unification of open structs or embedded closed structs.
// The result in each case is unchecked unification.
left := p.optionalsExpr(x.left, false)
right := p.optionalsExpr(x.right, false)
st.Elts = append(st.Elts, &ast.EmbedDecl{Expr: left})
st.Elts = append(st.Elts, &ast.EmbedDecl{Expr: right})
}
return skippedEllipsis
}
func (p *exporter) structure(x *structLit, addTempl bool) (ret *ast.StructLit, err *bottom) {
obj := ast.NewStruct()
if doEval(p.mode) {
x, err = x.expandFields(p.ctx)
if err != nil {
return nil, err
}
}
for _, a := range x.Arcs {
p.stack = append(p.stack, remap{
key: x,
from: a.Label,
to: nil,
syn: obj,
})
}
if x.emit != nil {
obj.Elts = append(obj.Elts, &ast.EmbedDecl{Expr: p.expr(x.emit)})
}
hasEllipsis := false
if p.showOptional() && x.optionals != nil &&
// Optional field constraints may be omitted if they were already
// applied and no more new fields may be added.
!(doEval(p.mode) && x.optionals.isEmpty() && p.isClosed(x)) {
hasEllipsis = p.optionals(len(x.Arcs) > 0, obj, x.optionals)
}
for i, a := range x.Arcs {
f := &ast.Field{
Label: p.label(a.Label),
}
// TODO: allow the removal of hidden fields. However, hidden fields
// that still used in incomplete expressions should not be removed
// (unless RequireConcrete is requested).
if a.optional {
// Optional fields are almost never concrete. We omit them in
// concrete mode to allow the user to use the -a option in eval
// without getting many errors.
if p.mode.omitOptional || p.mode.concrete {
continue
}
f.Optional = token.NoSpace.Pos()
}
if a.definition {
if p.mode.omitDefinitions || p.mode.concrete {
continue
}
if !internal.IsDefinition(f.Label) {
f.Token = token.ISA
}
}
if a.Label.IsHidden() && p.mode.concrete && p.mode.omitHidden {
continue
}
oldInDef := p.inDef
p.inDef = a.definition || p.inDef
if !doEval(p.mode) {
f.Value = p.expr(a.v)
} else {
f.Value = p.recExpr(a.v, x.at(p.ctx, i), a.optional)
}
p.inDef = oldInDef
if a.attrs != nil && !p.mode.omitAttrs {
for _, at := range a.attrs.attr {
f.Attrs = append(f.Attrs, &ast.Attribute{Text: at.text})
}
}
if p.mode.docs {
for _, d := range a.docs.appendDocs(nil) {
ast.AddComment(f, d)
break
}
}
obj.Elts = append(obj.Elts, f)
}
if !p.mode.concrete {
for _, v := range x.comprehensions {
switch c := v.comp.(type) {
case *fieldComprehension:
l := p.expr(c.key)
label, _ := l.(ast.Label)
opt := token.NoPos
if c.opt {
opt = token.NoSpace.Pos() // anything but token.NoPos
}
tok := token.COLON
if c.def && !internal.IsDefinition(label) {
tok = token.ISA
}
f := &ast.Field{
Label: label,
Optional: opt,
Token: tok,
Value: p.expr(c.val),
}
obj.Elts = append(obj.Elts, f)
case *structComprehension:
var clauses []ast.Clause
next := c.clauses
for {
if yield, ok := next.(*yield); ok {
obj.Elts = append(obj.Elts, &ast.Comprehension{
Clauses: clauses,
Value: p.expr(yield.value),
})
break
}
var y ast.Clause
y, next = p.clause(next)
clauses = append(clauses, y)
}
}
}
}
if hasEllipsis {
obj.Elts = append(obj.Elts, &ast.Ellipsis{})
}
return obj, nil
}
func hasBulk(a []ast.Decl) bool {
for _, d := range a {
if internal.IsBulkField(d) {
return true
}
}
return false
}
func (p *exporter) embedding(s *ast.StructLit, n value) (closed bool) {
switch x := n.(type) {
case *structLit:
st, err := p.structure(x, true)
if err != nil {
n = err
break
}
if hasBulk(st.Elts) {
s.Elts = append(s.Elts, internal.EmbedStruct(st))
} else {
s.Elts = append(s.Elts, st.Elts...)
}
return p.isClosed(x)
case *binaryExpr:
if x.Op != opUnifyUnchecked {
// should not happen
s.Elts = append(s.Elts, &ast.EmbedDecl{Expr: p.expr(x)})
return false
}
leftClosed := p.embedding(s, x.X)
rightClosed := p.embedding(s, x.Y)
return leftClosed || rightClosed
}
s.Elts = append(s.Elts, &ast.EmbedDecl{Expr: p.expr(n)})
return false
}
// quote quotes the given string.
func quote(str string, quote byte) string {
if strings.IndexByte(str, '\n') < 0 {
buf := []byte{quote}
buf = appendEscaped(buf, str, quote, true)
buf = append(buf, quote)
return string(buf)
}
buf := []byte{quote, quote, quote}
buf = append(buf, multiSep...)
buf = appendEscapeMulti(buf, str, quote)
buf = append(buf, quote, quote, quote)
return string(buf)
}
// TODO: consider the best indent strategy.
const multiSep = "\n "
func appendEscapeMulti(buf []byte, str string, quote byte) []byte {
// TODO(perf)
a := strings.Split(str, "\n")
for _, s := range a {
buf = appendEscaped(buf, s, quote, true)
buf = append(buf, multiSep...)
}
return buf
}
const lowerhex = "0123456789abcdef"
func appendEscaped(buf []byte, s string, quote byte, graphicOnly bool) []byte {
for width := 0; len(s) > 0; s = s[width:] {
r := rune(s[0])
width = 1
if r >= utf8.RuneSelf {
r, width = utf8.DecodeRuneInString(s)
}
if width == 1 && r == utf8.RuneError {
buf = append(buf, `\x`...)
buf = append(buf, lowerhex[s[0]>>4])
buf = append(buf, lowerhex[s[0]&0xF])
continue
}
buf = appendEscapedRune(buf, r, quote, graphicOnly)
}
return buf
}
func appendEscapedRune(buf []byte, r rune, quote byte, graphicOnly bool) []byte {
var runeTmp [utf8.UTFMax]byte
if r == rune(quote) || r == '\\' { // always backslashed
buf = append(buf, '\\')
buf = append(buf, byte(r))
return buf
}
// TODO(perf): IsGraphic calls IsPrint.
if strconv.IsPrint(r) || graphicOnly && strconv.IsGraphic(r) {
n := utf8.EncodeRune(runeTmp[:], r)
buf = append(buf, runeTmp[:n]...)
return buf
}
switch r {
case '\a':
buf = append(buf, `\a`...)
case '\b':
buf = append(buf, `\b`...)
case '\f':
buf = append(buf, `\f`...)
case '\n':
buf = append(buf, `\n`...)
case '\r':
buf = append(buf, `\r`...)
case '\t':
buf = append(buf, `\t`...)
case '\v':
buf = append(buf, `\v`...)
default:
switch {
case r < ' ':
// Invalid for strings, only bytes.
buf = append(buf, `\x`...)
buf = append(buf, lowerhex[byte(r)>>4])
buf = append(buf, lowerhex[byte(r)&0xF])
case r > utf8.MaxRune:
r = 0xFFFD
fallthrough
case r < 0x10000:
buf = append(buf, `\u`...)
for s := 12; s >= 0; s -= 4 {
buf = append(buf, lowerhex[r>>uint(s)&0xF])
}
default:
buf = append(buf, `\U`...)
for s := 28; s >= 0; s -= 4 {
buf = append(buf, lowerhex[r>>uint(s)&0xF])
}
}
}
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.Expr.(*numLit); ok {
if s.min == nil || s.minNum.X.Cmp(&n.X) != 1 {
s.min = x
s.minNum = n
}
return true
}
case opGeq:
if n, ok := x.Expr.(*numLit); ok {
if s.min == nil || s.minNum.X.Cmp(&n.X) == -1 {
s.min = x
s.minNum = n
}
return true
}
case opLss:
if n, ok := x.Expr.(*numLit); ok {
if s.max == nil || s.maxNum.X.Cmp(&n.X) != -1 {
s.max = x
s.maxNum = n
}
return true
}
case opLeq:
if n, ok := x.Expr.(*numLit); ok {
if s.max == nil || s.maxNum.X.Cmp(&n.X) == 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.X.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.X.IsZero() && s.min.Op == opGeq && s.minNum.X.Cmp(r.lo) == 0 {
switch s.maxNum.X.Cmp(r.hi) {
case 0:
if s.max.Op == opLeq {
s.max = nil
}
s.min = nil
return r.typ
case -1:
if !s.minNum.X.IsZero() {
s.min = nil
return r.typ
}
case 1:
}
} else if s.max.Op == opLeq && s.maxNum.X.Cmp(r.hi) == 0 {
switch s.minNum.X.Cmp(r.lo) {
case -1:
case 0:
if s.min.Op == opGeq {
s.min = nil
}
fallthrough
case 1:
s.max = nil
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.NewBinExpr(opMap[op], a, b)
}