cue/ast/astutil: add Expr to File conversion (ToFile)
The use of ast.File has been a bit awkward. With the
new Sanitize functionality (this package), however,
it comes in handy.
The idea is to move the entire API to a model where
the user would only care about expressions and let
calls like ToFile and Sanitize care about creating a
well-formed file.
Change-Id: Ib034c1074dc4c8c5513b22beb9c4342bae30e974
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6303
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/cue/ast/astutil/file.go b/cue/ast/astutil/file.go
new file mode 100644
index 0000000..e060b71
--- /dev/null
+++ b/cue/ast/astutil/file.go
@@ -0,0 +1,38 @@
+// 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 astutil
+
+import (
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/token"
+)
+
+// ToFile converts an expression to a File. It will create an import section for
+// any of the identifiers in x that refer to an import and will unshadow
+// references as appropriate.
+func ToFile(x ast.Expr) (*ast.File, error) {
+ var f *ast.File
+ if st, ok := x.(*ast.StructLit); ok {
+ f = &ast.File{Decls: st.Elts}
+ } else {
+ ast.SetRelPos(x, token.NoSpace)
+ f = &ast.File{Decls: []ast.Decl{&ast.EmbedDecl{Expr: x}}}
+ }
+
+ if err := Sanitize(f); err != nil {
+ return nil, err
+ }
+ return f, nil
+}
diff --git a/cue/ast/astutil/file_test.go b/cue/ast/astutil/file_test.go
new file mode 100644
index 0000000..a6aeb59
--- /dev/null
+++ b/cue/ast/astutil/file_test.go
@@ -0,0 +1,100 @@
+// 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 astutil_test
+
+import (
+ "strings"
+ "testing"
+
+ "cuelang.org/go/cue"
+ "cuelang.org/go/cue/ast"
+ "cuelang.org/go/cue/ast/astutil"
+ "cuelang.org/go/cue/format"
+ "cuelang.org/go/cue/token"
+ "github.com/google/go-cmp/cmp"
+)
+
+func TestToFile(t *testing.T) {
+ mat := ast.NewIdent("math")
+ mat.Node = ast.NewImport(nil, "math")
+ pi := ast.NewSel(mat, "Pi")
+
+ testCases := []struct {
+ desc string
+ expr ast.Expr
+ want string
+ }{{
+ desc: "add import",
+ expr: ast.NewBinExpr(token.ADD, ast.NewLit(token.INT, "1"), pi),
+ want: "4.14159265358979323846264",
+ }, {
+ desc: "resolve unresolved within struct",
+ expr: ast.NewStruct(
+ ast.NewIdent("a"), ast.NewString("foo"),
+ ast.NewIdent("b"), ast.NewIdent("a"),
+ ),
+ want: `
+a: "foo"
+b: "foo"
+`,
+ }, {
+ desc: "unshadow",
+ expr: func() ast.Expr {
+ ident := ast.NewIdent("a")
+ ref := ast.NewIdent("a")
+ ref.Node = ident
+
+ return ast.NewStruct(
+ ident, ast.NewString("bar"),
+ ast.NewIdent("c"), ast.NewStruct(
+ ast.NewIdent("a"), ast.NewString("foo"),
+ ast.NewIdent("b"), ref, // refers to outer `a`.
+ ))
+ }(),
+ want: `
+a: "bar"
+c: {
+ a: "foo"
+ b: "bar"
+}
+`,
+ }}
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ f, err := astutil.ToFile(tc.expr)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var r cue.Runtime
+
+ inst, err := r.CompileFile(f)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ b, err := format.Node(inst.Value().Syntax())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ got := string(b)
+ want := strings.TrimLeft(tc.want, "\n")
+ if got != want {
+ t.Error(cmp.Diff(got, want))
+ }
+ })
+ }
+}