blob: 4cbb49fc13d505eb9337df03dd63e2e055413b68 [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 (
"bytes"
"fmt"
"strings"
"cuelang.org/go/cue/ast"
"cuelang.org/go/cue/ast/astutil"
"cuelang.org/go/cue/literal"
"cuelang.org/go/cue/token"
"cuelang.org/go/internal/core/adt"
)
func (e *exporter) ident(x adt.Feature) *ast.Ident {
s := x.IdentString(e.ctx)
if !ast.IsValidIdent(s) {
panic(s + " is not a valid identifier")
}
return ast.NewIdent(s)
}
func (e *exporter) adt(expr adt.Expr, conjuncts []adt.Conjunct) ast.Expr {
switch x := expr.(type) {
case adt.Value:
return e.expr(x)
case *adt.ListLit:
a := []ast.Expr{}
for _, x := range x.Elems {
a = append(a, e.elem(x))
}
return ast.NewList(a...)
case *adt.StructLit:
// TODO: should we use pushFrame here?
// _, saved := e.pushFrame([]adt.Conjunct{adt.MakeConjunct(nil, x)})
// defer e.popFrame(saved)
// s := e.frame(0).scope
s := &ast.StructLit{}
for _, d := range x.Decls {
var a *ast.Alias
if orig, ok := d.Source().(*ast.Field); ok {
if alias, ok := orig.Value.(*ast.Alias); ok {
if e.valueAlias == nil {
e.valueAlias = map[*ast.Alias]*ast.Alias{}
}
a = &ast.Alias{Ident: ast.NewIdent(alias.Ident.Name)}
e.valueAlias[alias] = a
}
}
decl := e.decl(d)
if a != nil {
if f, ok := decl.(*ast.Field); ok {
a.Expr = f.Value
f.Value = a
}
}
s.Elts = append(s.Elts, decl)
}
return s
case *adt.FieldReference:
f := e.frame(x.UpCount)
entry := f.fields[x.Label]
name := x.Label.IdentString(e.ctx)
switch {
case entry.alias != "":
name = entry.alias
case !ast.IsValidIdent(name):
name = "X"
if x.Src != nil {
name = x.Src.Name
}
name = e.uniqueAlias(name)
entry.alias = name
}
ident := ast.NewIdent(name)
entry.references = append(entry.references, ident)
if f.fields != nil {
f.fields[x.Label] = entry
}
return ident
case *adt.ValueReference:
name := x.Label.IdentString(e.ctx)
if a, ok := x.Src.Node.(*ast.Alias); ok { // Should always pass
if b, ok := e.valueAlias[a]; ok {
name = b.Ident.Name
}
}
ident := ast.NewIdent(name)
return ident
case *adt.LabelReference:
// get potential label from Source. Otherwise use X.
f := e.frame(x.UpCount)
if f.field == nil {
// This can happen when the LabelReference is evaluated outside of
// normal evaluation, that is, if a pattern constraint or
// additional constraint is evaluated by itself.
return ast.NewIdent("string")
}
list, ok := f.field.Label.(*ast.ListLit)
if !ok || len(list.Elts) != 1 {
panic("label reference to non-pattern constraint field or invalid list")
}
name := ""
if a, ok := list.Elts[0].(*ast.Alias); ok {
name = a.Ident.Name
} else {
if x.Src != nil {
name = x.Src.Name
}
name = e.uniqueAlias(name)
list.Elts[0] = &ast.Alias{
Ident: ast.NewIdent(name),
Expr: list.Elts[0],
}
}
ident := ast.NewIdent(name)
ident.Scope = f.field
ident.Node = f.labelExpr
return ident
case *adt.DynamicReference:
// get potential label from Source. Otherwise use X.
name := "X"
f := e.frame(x.UpCount)
if d := f.field; d != nil {
if x.Src != nil {
name = x.Src.Name
}
name = e.getFieldAlias(d, name)
}
ident := ast.NewIdent(name)
ident.Scope = f.field
ident.Node = f.field
return ident
case *adt.ImportReference:
importPath := x.ImportPath.StringValue(e.index)
spec := ast.NewImport(nil, importPath)
info, _ := astutil.ParseImportSpec(spec)
name := info.PkgName
if x.Label != 0 {
name = x.Label.StringValue(e.index)
if name != info.PkgName {
spec.Name = ast.NewIdent(name)
}
}
ident := ast.NewIdent(name)
ident.Node = spec
return ident
case *adt.LetReference:
// TODO: Handle references that went out of scope. In case of aliases
// this means they may need to be reproduced locally. Most of these
// issues can be avoided by either fully expanding a configuration
// (export) or not at all (def).
//
i := len(e.stack) - 1 - int(x.UpCount) - 1
if i < 0 {
i = 0
}
f := &(e.stack[i])
let := f.let[x.X]
if let == nil {
if f.let == nil {
f.let = map[adt.Expr]*ast.LetClause{}
}
label := e.uniqueLetIdent(x.Label, x.X)
name := label.IdentString(e.ctx)
// A let may be added multiple times to the same scope as a result
// of how merging works. If that happens here it must be one
// originating from the same expression, and it is safe to drop.
for _, elt := range f.scope.Elts {
if a, ok := elt.(*ast.LetClause); ok {
if a.Ident.Name == name {
let = a
break
}
}
}
if let == nil {
let = &ast.LetClause{
Ident: e.ident(label),
Expr: e.expr(x.X),
}
f.scope.Elts = append(f.scope.Elts, let)
}
f.let[x.X] = let
}
ident := ast.NewIdent(let.Ident.Name)
ident.Node = let
ident.Scope = f.scope
return ident
case *adt.SelectorExpr:
return &ast.SelectorExpr{
X: e.expr(x.X),
Sel: e.stringLabel(x.Sel),
}
case *adt.IndexExpr:
return &ast.IndexExpr{
X: e.expr(x.X),
Index: e.expr(x.Index),
}
case *adt.SliceExpr:
var lo, hi ast.Expr
if x.Lo != nil {
lo = e.expr(x.Lo)
}
if x.Hi != nil {
hi = e.expr(x.Hi)
}
// TODO: Stride not yet? implemented.
// if x.Stride != nil {
// stride = e.expr(x.Stride)
// }
return &ast.SliceExpr{X: e.expr(x.X), Low: lo, High: hi}
case *adt.Interpolation:
var (
tripple = `"""`
openQuote = `"`
closeQuote = `"`
f = literal.String
)
if x.K&adt.BytesKind != 0 {
tripple = `'''`
openQuote = `'`
closeQuote = `'`
f = literal.Bytes
}
toString := func(v adt.Expr) string {
str := ""
switch x := v.(type) {
case *adt.String:
str = x.Str
case *adt.Bytes:
str = string(x.B)
}
return str
}
t := &ast.Interpolation{}
f = f.WithGraphicOnly()
indent := ""
// TODO: mark formatting in interpolation itself.
for i := 0; i < len(x.Parts); i += 2 {
if strings.IndexByte(toString(x.Parts[i]), '\n') >= 0 {
f = f.WithTabIndent(len(e.stack))
indent = strings.Repeat("\t", len(e.stack))
openQuote = tripple + "\n" + indent
closeQuote = tripple
break
}
}
prefix := openQuote
suffix := `\(`
for i, elem := range x.Parts {
if i%2 == 1 {
t.Elts = append(t.Elts, e.expr(elem))
} else {
// b := strings.Builder{}
buf := []byte(prefix)
str := toString(elem)
buf = f.AppendEscaped(buf, str)
if i == len(x.Parts)-1 {
if len(closeQuote) > 1 {
buf = append(buf, '\n')
buf = append(buf, indent...)
}
buf = append(buf, closeQuote...)
} else {
if bytes.HasSuffix(buf, []byte("\n")) {
buf = append(buf, indent...)
}
buf = append(buf, suffix...)
}
t.Elts = append(t.Elts, &ast.BasicLit{
Kind: token.STRING,
Value: string(buf),
})
}
prefix = ")"
}
return t
case *adt.BoundExpr:
return &ast.UnaryExpr{
Op: x.Op.Token(),
X: e.expr(x.Expr),
}
case *adt.UnaryExpr:
return &ast.UnaryExpr{
Op: x.Op.Token(),
X: e.expr(x.X),
}
case *adt.BinaryExpr:
return &ast.BinaryExpr{
Op: x.Op.Token(),
X: e.expr(x.X),
Y: e.expr(x.Y),
}
case *adt.CallExpr:
a := []ast.Expr{}
for _, arg := range x.Args {
v := e.expr(arg)
if v == nil {
e.expr(arg)
panic("")
}
a = append(a, v)
}
fun := e.expr(x.Fun)
return &ast.CallExpr{Fun: fun, Args: a}
case *adt.DisjunctionExpr:
a := []ast.Expr{}
for _, d := range x.Values {
v := e.expr(d.Val)
if d.Default {
v = &ast.UnaryExpr{Op: token.MUL, X: v}
}
a = append(a, v)
}
return ast.NewBinExpr(token.OR, a...)
default:
panic(fmt.Sprintf("unknown field %T", x))
}
}
func (e *exporter) decl(d adt.Decl) ast.Decl {
switch x := d.(type) {
case adt.Elem:
return e.elem(x)
case *adt.Field:
e.setDocs(x)
f := &ast.Field{
Label: e.stringLabel(x.Label),
}
frame := e.frame(0)
entry := frame.fields[x.Label]
entry.field = f
entry.node = f.Value
frame.fields[x.Label] = entry
f.Value = e.expr(x.Value)
// extractDocs(nil)
return f
case *adt.OptionalField:
e.setDocs(x)
f := &ast.Field{
Label: e.stringLabel(x.Label),
Optional: token.NoSpace.Pos(),
}
frame := e.frame(0)
entry := frame.fields[x.Label]
entry.field = f
entry.node = f.Value
frame.fields[x.Label] = entry
f.Value = e.expr(x.Value)
// extractDocs(nil)
return f
case *adt.BulkOptionalField:
e.setDocs(x)
// set bulk in frame.
frame := e.frame(0)
expr := e.expr(x.Filter)
frame.labelExpr = expr // see astutil.Resolve.
if x.Label != 0 {
expr = &ast.Alias{Ident: e.ident(x.Label), Expr: expr}
}
f := &ast.Field{
Label: ast.NewList(expr),
}
frame.field = f
f.Value = e.expr(x.Value)
return f
case *adt.DynamicField:
e.setDocs(x)
key := e.expr(x.Key)
if _, ok := key.(*ast.Interpolation); !ok {
key = &ast.ParenExpr{X: key}
}
f := &ast.Field{
Label: key.(ast.Label),
}
frame := e.frame(0)
frame.field = f
frame.labelExpr = key
// extractDocs(nil)
f.Value = e.expr(x.Value)
return f
default:
panic(fmt.Sprintf("unknown field %T", x))
}
}
func (e *exporter) elem(d adt.Elem) ast.Expr {
switch x := d.(type) {
case adt.Expr:
return e.expr(x)
case *adt.Ellipsis:
t := &ast.Ellipsis{}
if x.Value != nil {
t.Type = e.expr(x.Value)
}
return t
case adt.Yielder:
return e.comprehension(x)
default:
panic(fmt.Sprintf("unknown field %T", x))
}
}
func (e *exporter) comprehension(y adt.Yielder) ast.Expr {
c := &ast.Comprehension{}
for {
switch x := y.(type) {
case *adt.ForClause:
value := e.ident(x.Value)
clause := &ast.ForClause{
Value: value,
Source: e.expr(x.Src),
}
c.Clauses = append(c.Clauses, clause)
_, saved := e.pushFrame(nil)
defer e.popFrame(saved)
if x.Key != 0 {
key := e.ident(x.Key)
clause.Key = key
e.addField(x.Key, nil, clause)
}
e.addField(x.Value, nil, clause)
y = x.Dst
case *adt.IfClause:
clause := &ast.IfClause{Condition: e.expr(x.Condition)}
c.Clauses = append(c.Clauses, clause)
y = x.Dst
case *adt.LetClause:
clause := &ast.LetClause{
Ident: e.ident(x.Label),
Expr: e.expr(x.Expr),
}
c.Clauses = append(c.Clauses, clause)
_, saved := e.pushFrame(nil)
defer e.popFrame(saved)
e.addField(x.Label, nil, clause)
y = x.Dst
case *adt.ValueClause:
v := e.expr(x.StructLit)
if _, ok := v.(*ast.StructLit); !ok {
v = ast.NewStruct(ast.Embed(v))
}
c.Value = v
return c
default:
panic(fmt.Sprintf("unknown field %T", x))
}
}
}