blob: 6c88b5bf4404410445c59cbc26c19af4b0d72f2b [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 export
import (
"fmt"
"math/rand"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/ast/astutil"
"cuelang.org/go/cue/errors"
"cuelang.org/go/internal"
"cuelang.org/go/internal/core/adt"
"cuelang.org/go/internal/core/eval"
"cuelang.org/go/internal/core/walk"
)
const debug = false
type Profile struct {
Simplify bool
// Final reports incomplete errors as errors.
Final bool
// TakeDefaults is used in Value mode to drop non-default values.
TakeDefaults bool
ShowOptional bool
ShowDefinitions bool
// ShowHidden forces the inclusion of hidden fields when these would
// otherwise be omitted. Only hidden fields from the current package are
// included.
ShowHidden bool
ShowDocs bool
ShowAttributes bool
// ShowErrors treats errors as values and will not percolate errors up.
ShowErrors bool
// Use unevaluated conjuncts for these error types
// IgnoreRecursive
// TODO: recurse over entire tree to determine transitive closure
// of what needs to be printed.
// IncludeDependencies bool
}
var Simplified = &Profile{
Simplify: true,
ShowDocs: true,
}
var Final = &Profile{
Simplify: true,
TakeDefaults: true,
Final: true,
}
var Raw = &Profile{
ShowOptional: true,
ShowDefinitions: true,
ShowHidden: true,
ShowDocs: true,
}
var All = &Profile{
Simplify: true,
ShowOptional: true,
ShowDefinitions: true,
ShowHidden: true,
ShowDocs: true,
ShowAttributes: true,
}
// Concrete
// Def exports v as a definition.
func Def(r adt.Runtime, pkgID string, v *adt.Vertex) (*ast.File, errors.Error) {
return All.Def(r, pkgID, v)
}
// Def exports v as a definition.
func (p *Profile) Def(r adt.Runtime, pkgID string, v *adt.Vertex) (*ast.File, errors.Error) {
e := newExporter(p, r, pkgID, v)
e.markUsedFeatures(v)
isDef := v.IsRecursivelyClosed()
if isDef {
e.inDefinition++
}
expr := e.expr(v)
if isDef {
e.inDefinition--
if v.Kind() == adt.StructKind {
expr = ast.NewStruct(
ast.Embed(ast.NewIdent("_#def")),
ast.NewIdent("_#def"), expr,
)
}
}
return e.toFile(v, expr)
}
func Expr(r adt.Runtime, pkgID string, n adt.Expr) (ast.Expr, errors.Error) {
return Simplified.Expr(r, pkgID, n)
}
func (p *Profile) Expr(r adt.Runtime, pkgID string, n adt.Expr) (ast.Expr, errors.Error) {
e := newExporter(p, r, pkgID, nil)
e.markUsedFeatures(n)
return e.expr(n), nil
}
func (e *exporter) toFile(v *adt.Vertex, x ast.Expr) (*ast.File, errors.Error) {
f := &ast.File{}
pkgName := ""
pkg := &ast.Package{}
for _, c := range v.Conjuncts {
f, _ := c.Source().(*ast.File)
if f == nil {
continue
}
if _, name, _ := internal.PackageInfo(f); name != "" {
pkgName = name
}
if e.cfg.ShowDocs {
if doc := internal.FileComment(f); doc != nil {
ast.AddComment(pkg, doc)
}
}
}
if pkgName != "" {
pkg.Name = ast.NewIdent(pkgName)
f.Decls = append(f.Decls, pkg)
}
switch st := x.(type) {
case nil:
panic("null input")
case *ast.StructLit:
f.Decls = append(f.Decls, st.Elts...)
default:
f.Decls = append(f.Decls, &ast.EmbedDecl{Expr: x})
}
if err := astutil.Sanitize(f); err != nil {
err := errors.Promote(err, "export")
return f, errors.Append(e.errs, err)
}
return f, nil
}
// File
func Vertex(r adt.Runtime, pkgID string, n *adt.Vertex) (*ast.File, errors.Error) {
return Simplified.Vertex(r, pkgID, n)
}
func (p *Profile) Vertex(r adt.Runtime, pkgID string, n *adt.Vertex) (*ast.File, errors.Error) {
e := exporter{
ctx: eval.NewContext(r, nil),
cfg: p,
index: r,
pkgID: pkgID,
}
e.markUsedFeatures(n)
v := e.value(n, n.Conjuncts...)
return e.toFile(n, v)
}
func Value(r adt.Runtime, pkgID string, n adt.Value) (ast.Expr, errors.Error) {
return Simplified.Value(r, pkgID, n)
}
// Should take context.
func (p *Profile) Value(r adt.Runtime, pkgID string, n adt.Value) (ast.Expr, errors.Error) {
e := exporter{
ctx: eval.NewContext(r, nil),
cfg: p,
index: r,
pkgID: pkgID,
}
e.markUsedFeatures(n)
v := e.value(n)
return v, e.errs
}
type exporter struct {
cfg *Profile // Make value todo
errs errors.Error
ctx *adt.OpContext
index adt.StringIndexer
rand *rand.Rand
// For resolving up references.
stack []frame
inDefinition int // for close() wrapping.
// hidden label handling
pkgID string
hidden map[string]adt.Feature // adt.InvalidFeatures means more than one.
// If a used feature maps to an expression, it means it is assigned to a
// unique let expression.
usedFeature map[adt.Feature]adt.Expr
labelAlias map[adt.Expr]adt.Feature
valueAlias map[*ast.Alias]*ast.Alias
usedHidden map[string]bool
}
func newExporter(p *Profile, r adt.Runtime, pkgID string, v *adt.Vertex) *exporter {
return &exporter{
cfg: p,
ctx: eval.NewContext(r, v),
index: r,
pkgID: pkgID,
}
}
func (e *exporter) markUsedFeatures(x adt.Expr) {
e.usedFeature = make(map[adt.Feature]adt.Expr)
w := &walk.Visitor{}
w.Before = func(n adt.Node) bool {
switch x := n.(type) {
case *adt.Vertex:
if !x.IsData() {
for _, c := range x.Conjuncts {
w.Expr(c.Expr())
}
}
case *adt.DynamicReference:
if e.labelAlias == nil {
e.labelAlias = make(map[adt.Expr]adt.Feature)
}
// TODO: add preferred label.
e.labelAlias[x.Label] = adt.InvalidLabel
case *adt.LabelReference:
}
return true
}
w.Feature = func(f adt.Feature, src adt.Node) {
_, ok := e.usedFeature[f]
switch x := src.(type) {
case *adt.LetReference:
if !ok {
e.usedFeature[f] = x.X
}
default:
e.usedFeature[f] = nil
}
}
w.Expr(x)
}
func (e *exporter) getFieldAlias(f *ast.Field, name string) string {
a, ok := f.Label.(*ast.Alias)
if !ok {
a = &ast.Alias{
Ident: ast.NewIdent(e.uniqueAlias(name)),
Expr: f.Label.(ast.Expr),
}
f.Label = a
}
return a.Ident.Name
}
func setFieldAlias(f *ast.Field, name string) {
if _, ok := f.Label.(*ast.Alias); !ok {
f.Label = &ast.Alias{
Ident: ast.NewIdent(name),
Expr: f.Label.(ast.Expr),
}
}
}
// uniqueLetIdent returns a name for a let identifier that uniquely identifies
// the given expression. If the preferred name is already taken, a new globally
// unique name of the form base_X ... base_XXXXXXXXXXXXXX is generated.
//
// It prefers short extensions over large ones, while ensuring the likelihood of
// fast termination is high. There are at least two digits to make it visually
// clearer this concerns a generated number.
//
func (e exporter) uniqueLetIdent(f adt.Feature, x adt.Expr) adt.Feature {
if e.usedFeature[f] == x {
return f
}
f, _ = e.uniqueFeature(f.IdentString(e.ctx))
e.usedFeature[f] = x
return f
}
func (e exporter) uniqueAlias(name string) string {
f := adt.MakeIdentLabel(e.ctx, name, "")
if _, ok := e.usedFeature[f]; !ok {
e.usedFeature[f] = nil
return name
}
_, name = e.uniqueFeature(f.IdentString(e.ctx))
return name
}
func (e exporter) uniqueFeature(base string) (f adt.Feature, name string) {
if e.rand == nil {
e.rand = rand.New(rand.NewSource(808))
}
const mask = 0xff_ffff_ffff_ffff // max bits; stay clear of int64 overflow
const shift = 4 // rate of growth
for n := int64(0x10); ; n = int64(mask&((n<<shift)-1)) + 1 {
num := e.rand.Intn(int(n))
name := fmt.Sprintf("%s_%01X", base, num)
f := adt.MakeIdentLabel(e.ctx, name, "")
if _, ok := e.usedFeature[f]; !ok {
e.usedFeature[f] = nil
return f, name
}
}
}
type completeFunc func(scope *ast.StructLit, m adt.Node)
type frame struct {
scope *ast.StructLit
todo []completeFunc
docSources []adt.Conjunct
// For resolving dynamic fields.
field *ast.Field
labelExpr ast.Expr
upCount int32 // for off-by-one handling
// labeled fields
fields map[adt.Feature]entry
let map[adt.Expr]*ast.LetClause
// field to new field
mapped map[adt.Node]ast.Node
}
type entry struct {
alias string
field *ast.Field
node ast.Node // How to reference. See astutil.Resolve
references []*ast.Ident
}
func (e *exporter) addField(label adt.Feature, f *ast.Field, n ast.Node) {
frame := e.top()
entry := frame.fields[label]
entry.field = f
entry.node = n
frame.fields[label] = entry
}
func (e *exporter) addEmbed(x ast.Expr) {
frame := e.top()
frame.scope.Elts = append(frame.scope.Elts, x)
}
func (e *exporter) pushFrame(conjuncts []adt.Conjunct) (s *ast.StructLit, saved []frame) {
saved = e.stack
s = &ast.StructLit{}
e.stack = append(e.stack, frame{
scope: s,
mapped: map[adt.Node]ast.Node{},
fields: map[adt.Feature]entry{},
docSources: conjuncts,
})
return s, saved
}
func (e *exporter) popFrame(saved []frame) {
top := e.stack[len(e.stack)-1]
for _, f := range top.fields {
node := f.node
if f.alias != "" && f.field != nil {
setFieldAlias(f.field, f.alias)
node = f.field
}
for _, r := range f.references {
r.Node = node
}
}
e.stack = saved
}
func (e *exporter) top() *frame {
return &(e.stack[len(e.stack)-1])
}
func (e *exporter) frame(upCount int32) *frame {
for i := len(e.stack) - 1; i >= 0; i-- {
f := &(e.stack[i])
if upCount <= (f.upCount - 1) {
return f
}
upCount -= f.upCount
}
if debug {
// This may be valid when exporting incomplete references. These are
// not yet handled though, so find a way to catch them when debugging
// printing of values that are supposed to be complete.
panic("unreachable reference")
}
return &frame{}
}
func (e *exporter) setDocs(x adt.Node) {
f := e.stack[len(e.stack)-1]
f.docSources = []adt.Conjunct{adt.MakeRootConjunct(nil, x)}
e.stack[len(e.stack)-1] = f
}
// func (e *Exporter) promise(upCount int32, f completeFunc) {
// e.todo = append(e.todo, f)
// }
func (e *exporter) errf(format string, args ...interface{}) *ast.BottomLit {
err := &exporterError{}
e.errs = errors.Append(e.errs, err)
return &ast.BottomLit{}
}
type errTODO errors.Error
type exporterError struct {
errTODO
}