blob: 8013a05fad137437215b4cbc66c73e1805a2b34e [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"
"strconv"
"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 := e.ctx.IndexToString(int64(x.Index()))
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 {
s.Elts = append(s.Elts, e.decl(d))
}
return s
case *adt.FieldReference:
f := e.frame(x.UpCount)
ident := e.ident(x.Label)
entry := f.fields[x.Label]
entry.references = append(entry.references, ident)
return ident
case *adt.LabelReference:
// get potential label from Source. Otherwise use X.
f := e.frame(x.UpCount)
var ident *ast.Ident
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")
}
if a, ok := list.Elts[0].(*ast.Alias); ok {
ident = ast.NewIdent(a.Ident.Name)
} else {
ident = ast.NewIdent("X" + strconv.Itoa(e.unique))
e.unique++
list.Elts[0] = &ast.Alias{
Ident: ast.NewIdent(ident.Name),
Expr: list.Elts[0],
}
}
ident.Scope = f.field
ident.Node = f.labelExpr
return ident
case *adt.DynamicReference:
// get potential label from Source. Otherwise use X.
ident := ast.NewIdent("X")
f := e.frame(x.UpCount)
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:
// - rename if necessary
// - look in to reusing the mechanism of the old evaluator
//
// Either way, we need a better mechanism. References may go 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{}
}
let = &ast.LetClause{
Ident: e.ident(x.Label),
Expr: e.expr(x.X),
}
f.let[x.X] = let
f.scope.Elts = append(f.scope.Elts, let)
}
ident := e.ident(x.Label)
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:
t := &ast.Interpolation{}
f := literal.String.WithGraphicOnly() // TODO: also support bytes
openQuote := `"`
closeQuote := `"`
indent := ""
// TODO: mark formatting in interpolation itself.
for i := 0; i < len(x.Parts); i += 2 {
str := x.Parts[i].(*adt.String).Str
if strings.IndexByte(str, '\n') >= 0 {
f = f.WithTabIndent(len(e.stack))
indent = strings.Repeat("\t", len(e.stack))
openQuote = `"""` + "\n" + indent
closeQuote = `"""`
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 := elem.(*adt.String).Str
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),
Value: e.expr(x.Value),
}
e.addField(x.Label, f.Value)
// extractDocs(nil)
return f
case *adt.OptionalField:
e.setDocs(x)
f := &ast.Field{
Label: e.stringLabel(x.Label),
Optional: token.NoSpace.Pos(),
Value: e.expr(x.Value),
}
e.addField(x.Label, f.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, clause)
}
e.addField(x.Value, 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{Expr: e.expr(x.Expr)}
c.Clauses = append(c.Clauses, clause)
_, saved := e.pushFrame(nil)
defer e.popFrame(saved)
e.addField(x.Label, 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))
}
}
}