cue: implement marshaling and restoring instances

Also fixed bug in export that prevented proper
raw exporting.

Change-Id: I128760ed89efa1907e15b999722806267ec6144a
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2707
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/build.go b/cue/build.go
index c0fb628..f0fb2a5 100644
--- a/cue/build.go
+++ b/cue/build.go
@@ -15,12 +15,16 @@
 package cue
 
 import (
+	"bytes"
+	"compress/gzip"
+	"encoding/gob"
 	"path"
 	"strconv"
 
 	"cuelang.org/go/cue/ast"
 	"cuelang.org/go/cue/build"
 	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/format"
 	"cuelang.org/go/cue/token"
 )
 
@@ -29,9 +33,8 @@
 // Any operation that involves two Values or Instances should originate from
 // the same Runtime.
 type Runtime struct {
-	Context *build.Context
+	Context *build.Context // TODO: remove
 	idx     *index
-	ctxt    *build.Context
 }
 
 func dummyLoad(token.Pos, string) *build.Instance { return nil }
@@ -56,6 +59,73 @@
 	return inst, nil
 }
 
+type instanceData struct {
+	Filename string
+	PkgPath  string
+	Data     []byte
+}
+
+// Unmarshal creates an Instance from bytes generated by the MarshalBinary
+// method of an instance.
+func (r *Runtime) Unmarshal(b []byte) (*Instance, error) {
+	reader, err := gzip.NewReader(bytes.NewReader(b))
+	if err != nil {
+		return nil, errors.Newf(token.NoPos, "restore failed: %v", err)
+	}
+	version := [1]byte{}
+	_, _ = reader.Read(version[:])
+
+	data := &instanceData{}
+	if err = gob.NewDecoder(reader).Decode(data); err != nil {
+		return nil, errors.Newf(token.NoPos, "restore failed: %v", err)
+	}
+
+	inst, err := r.Compile(data.Filename, data.Data)
+	if inst != nil {
+		inst.ImportPath = data.PkgPath
+	}
+	return inst, err
+}
+
+// MarshalBinary marshals c into binary data.
+func (inst *Instance) MarshalBinary() (b []byte, err error) {
+	ctx := inst.index.newContext()
+	n := export(ctx, inst.rootValue, options{raw: true})
+
+	file, ok := n.(*ast.File)
+	if !ok {
+		file = &ast.File{}
+		if obj, ok := n.(*ast.StructLit); ok {
+			file.Decls = append(file.Decls, obj.Elts...)
+		} else {
+			file.Decls = append(file.Decls, &ast.EmbedDecl{Expr: n.(ast.Expr)})
+		}
+	}
+	if inst.Name != "" {
+		file.Name = ast.NewIdent(inst.Name)
+	}
+
+	b, err = format.Node(file)
+	if err != nil {
+		return nil, err
+	}
+
+	buf := &bytes.Buffer{}
+	zw := gzip.NewWriter(buf)
+	_, _ = zw.Write([]byte{0}) // version marker
+	enc := gob.NewEncoder(zw)
+	err = enc.Encode(&instanceData{
+		Filename: inst.Value().Pos().Filename(),
+		PkgPath:  inst.ImportPath,
+		Data:     b,
+	})
+	if err != nil {
+		return nil, err
+	}
+	err = zw.Close()
+	return buf.Bytes(), err
+}
+
 // Compile compiles the given source into an Instance. The source code may be
 // provided as a string, byte slice, io.Reader. The name is used as the file
 // name in position information. The source may import builtin packages. Use
@@ -119,15 +189,7 @@
 //
 // Deprecated: use Compile
 func (r *Runtime) Parse(name string, source interface{}) (*Instance, error) {
-	ctx := r.Context
-	if ctx == nil {
-		ctx = build.NewContext()
-	}
-	p := ctx.NewInstance(name, dummyLoad)
-	if err := p.AddFile(name, source); err != nil {
-		return nil, err
-	}
-	return r.complete(p)
+	return r.Compile(name, source)
 }
 
 // Build creates an Instance from the given build.Instance. A returned Instance
diff --git a/cue/build_test.go b/cue/build_test.go
index cb3607c..8377e8a 100644
--- a/cue/build_test.go
+++ b/cue/build_test.go
@@ -24,6 +24,73 @@
 	"cuelang.org/go/cue/token"
 )
 
+func TestMarshalling(t *testing.T) {
+	testCases := []struct {
+		filename string
+		input    string
+		pkg      string
+	}{{
+		filename: "foo.cue",
+		pkg:      "foo",
+		input: `package foo
+
+		A: int
+		B: string
+		`,
+	}, {
+		filename: "bar.cue",
+		pkg:      "bar",
+		input: `package bar
+
+		"Hello world!"
+		`,
+	}, {
+		filename: "qux.cue",
+		input: `
+			"Hello world!"
+		`,
+	}, {
+		filename: "baz.cue",
+		pkg:      "baz",
+		input: `package baz
+
+		import "strings"
+
+		a: strings.TrimSpace("  Hello world!  ")
+		`}}
+	for _, tc := range testCases {
+		t.Run(tc.filename, func(t *testing.T) {
+			r := &Runtime{}
+			inst, err := r.Compile(tc.filename, tc.input)
+			if err != nil {
+				t.Fatal(err)
+			}
+			inst.ImportPath = "test/pkg"
+			got := fmt.Sprint(inst.Value())
+
+			b, err := inst.MarshalBinary()
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			r2 := &Runtime{}
+			inst, err = r2.Unmarshal(b)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			if inst.ImportPath != "test/pkg" {
+				t.Error("import path was not restored")
+			}
+			want := fmt.Sprint(inst.Value())
+
+			if got != want {
+				t.Errorf("\ngot:  %q;\nwant: %q", got, want)
+			}
+		})
+	}
+}
+
 func TestFromExpr(t *testing.T) {
 	testCases := []struct {
 		expr ast.Expr
diff --git a/cue/export.go b/cue/export.go
index df4c898..61454d3 100644
--- a/cue/export.go
+++ b/cue/export.go
@@ -44,6 +44,7 @@
 
 	value := e.expr(v)
 	if len(e.imports) == 0 {
+		// TODO: unwrap structs?
 		return value
 	}
 	imports := make([]string, 0, len(e.imports))
@@ -73,7 +74,6 @@
 		})
 	}
 
-	// TODO: should we unwrap structs?
 	if obj, ok := value.(*ast.StructLit); ok {
 		file.Decls = append(file.Decls, obj.Elts...)
 	} else {
@@ -275,7 +275,13 @@
 		}
 
 	case *callExpr:
-		call := &ast.CallExpr{Fun: p.expr(x.x)}
+		call := &ast.CallExpr{}
+		b := x.x.evalPartial(p.ctx)
+		if b, ok := b.(*builtin); ok {
+			call.Fun = p.expr(b)
+		} else {
+			call.Fun = p.expr(x.x)
+		}
 		for _, a := range x.args {
 			call.Args = append(call.Args, p.expr(a))
 		}
@@ -335,14 +341,14 @@
 		obj := &ast.StructLit{}
 		if doEval(p.mode) {
 			x = x.expandFields(p.ctx)
-			for _, a := range x.arcs {
-				p.stack = append(p.stack, remap{
-					key:  x,
-					from: a.feature,
-					to:   nil,
-					syn:  obj,
-				})
-			}
+		}
+		for _, a := range x.arcs {
+			p.stack = append(p.stack, remap{
+				key:  x,
+				from: a.feature,
+				to:   nil,
+				syn:  obj,
+			})
 		}
 		if x.emit != nil {
 			obj.Elts = append(obj.Elts, &ast.EmbedDecl{Expr: p.expr(x.emit)})
diff --git a/cue/export_test.go b/cue/export_test.go
index c3ca25b..9da8857 100644
--- a/cue/export_test.go
+++ b/cue/export_test.go
@@ -305,7 +305,8 @@
 				}][a]
 				a: int
 				c: 1
-			}`)}}
+			}`),
+	}}
 	for _, tc := range testCases {
 		t.Run("", func(t *testing.T) {
 			body := fmt.Sprintf("Test: %s", tc.in)
@@ -350,6 +351,16 @@
 		in: `
 		import "strings"
 
+		a: strings.TrimSpace("  c  ")
+		`,
+		out: unindent(`
+		import "strings"
+
+		a: strings.TrimSpace("  c  ")`),
+	}, {
+		in: `
+		import "strings"
+
 		stringsx = strings
 
 		a: {
@@ -361,6 +372,16 @@
 
 		STRINGS = strings
 		a strings: STRINGS.ContainsAny("c")`),
+	}, {
+		in: `
+			a: b - 100
+			b: a + 100
+		`,
+		out: unindent(`
+		{
+			a: b - 100
+			b: a + 100
+		}`),
 	}}
 	for _, tc := range testCases {
 		t.Run("", func(t *testing.T) {
@@ -372,8 +393,8 @@
 			v := inst.Value()
 			ctx := r.index().newContext()
 
-			opts := options{raw: false}
-			b, err := format.Node(export(ctx, v.eval(ctx), opts), format.Simplify())
+			opts := options{raw: true}
+			b, err := format.Node(export(ctx, v.path.v, opts), format.Simplify())
 			if err != nil {
 				log.Fatal(err)
 			}