cue/build: add package

Change-Id: I572b8279b51b8816425923dc7f2a7f3d31853ad3
diff --git a/cue/build/context.go b/cue/build/context.go
new file mode 100644
index 0000000..b918833
--- /dev/null
+++ b/cue/build/context.go
@@ -0,0 +1,136 @@
+// Copyright 2018 The 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 build defines data types and utilities for defining CUE configuration
+// instances.
+//
+// This package enforces the rules regarding packages and instances as defined
+// in the spec, but it leaves any other details, as well as handling of modules,
+// up to the implementation.
+//
+// A full implementation of instance loading can be found in the loader package.
+//
+// WARNING: this packages may change. It is fine to use load and cue, who both
+// use this package.
+package build
+
+import (
+	"context"
+
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/cue/token"
+)
+
+// A Context keeps track of state of building instances and caches work.
+type Context struct {
+	ctxt context.Context
+	fset *token.FileSet
+
+	loader       LoadFunc
+	parseOptions []parser.Option
+
+	initialized bool
+
+	imports map[string]*Instance
+}
+
+// NewInstance creates an instance for this Context.
+func (c *Context) NewInstance(dir string, f LoadFunc) *Instance {
+	if f == nil {
+		f = c.loader
+	}
+	return &Instance{
+		ctxt:     c,
+		loadFunc: f,
+		Dir:      dir,
+	}
+}
+
+// Complete finishes the initialization of an instance. All files must have
+// been added with AddFile before this call.
+func (inst *Instance) Complete() error {
+	if inst.done {
+		return inst.Err
+	}
+	inst.done = true
+
+	err := inst.complete()
+	if err != nil {
+		inst.Err = err
+		inst.Incomplete = true
+	}
+	return err
+}
+
+func (c *Context) init() {
+	if !c.initialized {
+		c.initialized = true
+		c.ctxt = context.Background()
+		c.initialized = true
+		c.imports = map[string]*Instance{}
+		c.fset = token.NewFileSet()
+	}
+}
+
+// Options:
+// - certain parse modes
+// - parallellism
+// - error handler (allows cancelling the context)
+// - file set.
+
+// NewContext creates a new build context.
+//
+// All instances must be created with a context.
+func NewContext(opts ...Option) *Context {
+	c := &Context{}
+	for _, o := range opts {
+		o(c)
+	}
+	c.init()
+	return c
+}
+
+// Pos returns position information for a token.Pos.
+func (c *Context) Pos(pos token.Pos) token.Position {
+	if c.fset == nil {
+		return token.Position{}
+	}
+	return c.fset.Position(pos)
+}
+
+// FileSet reports the file set used for parsing files.
+func (c *Context) FileSet() *token.FileSet {
+	c.init()
+	return c.fset
+}
+
+// PurgeCache purges the instance cache.
+func (c *Context) PurgeCache() {
+	for name := range c.imports {
+		delete(c.imports, name)
+	}
+}
+
+// Option define build options.
+type Option func(c *Context)
+
+// ParseOptions sets parsing options.
+func ParseOptions(mode ...parser.Option) Option {
+	return func(c *Context) { c.parseOptions = mode }
+}
+
+// Loader sets parsing options.
+func Loader(f LoadFunc) Option {
+	return func(c *Context) { c.loader = f }
+}
diff --git a/cue/build/doc.go b/cue/build/doc.go
new file mode 100644
index 0000000..52421c6
--- /dev/null
+++ b/cue/build/doc.go
@@ -0,0 +1,16 @@
+// Copyright 2018 The 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 build defines collections of CUE files to build an instance.
+package build // import "cuelang.org/go/cue/build"
diff --git a/cue/build/import.go b/cue/build/import.go
new file mode 100644
index 0000000..bb29061
--- /dev/null
+++ b/cue/build/import.go
@@ -0,0 +1,154 @@
+// Copyright 2018 The 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 build
+
+import (
+	"log"
+	"sort"
+	"strconv"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/token"
+)
+
+type LoadFunc func(path string) *Instance
+
+func (inst *Instance) complete() error {
+	// TODO: handle case-insensitive collisions.
+	// dir := inst.Dir
+	// names := []string{}
+	// for _, src := range sources {
+	// 	names = append(names, src.path)
+	// }
+	// f1, f2 := str.FoldDup(names)
+	// if f1 != "" {
+	// 	return nil, fmt.Errorf("case-insensitive file name collision: %q and %q", f1, f2)
+	// }
+
+	var (
+		c        = inst.ctxt
+		fset     = c.FileSet()
+		imported = map[string][]token.Position{}
+	)
+
+	for _, f := range inst.Files {
+		for _, decl := range f.Decls {
+			d, ok := decl.(*ast.ImportDecl)
+			if !ok {
+				continue
+			}
+			for _, spec := range d.Specs {
+				quoted := spec.Path.Value
+				path, err := strconv.Unquote(quoted)
+				if err != nil {
+					// TODO: remove panic
+					log.Panicf("%s: parser returned invalid quoted string: <%s>", f.Filename, quoted)
+				}
+				imported[path] = append(imported[path], fset.Position(spec.Pos()))
+			}
+		}
+	}
+
+	paths := make([]string, 0, len(imported))
+	for path := range imported {
+		paths = append(paths, path)
+		if path == "" {
+			return errors.E(imported[path], "empty import path")
+		}
+	}
+
+	sort.Strings(paths)
+
+	if inst.loadFunc != nil {
+		for i, path := range paths {
+			isLocal := IsLocalImport(path)
+			if isLocal {
+				// path = dirToImportPath(filepath.Join(dir, path))
+			}
+
+			imp := c.imports[path]
+			if imp == nil {
+				imp = inst.loadFunc(path)
+				if imp == nil {
+					continue
+				}
+				if imp.Err != nil {
+					if len(imported[path]) > 0 {
+						imp.Err = errors.Augment(imp.Err, imported[path][0])
+					}
+					return imp.Err
+				}
+				imp.ImportPath = path
+				// imp.parent = inst
+				c.imports[path] = imp
+				// imp.parent = nil
+			} else if imp.parent != nil {
+				// TODO: report a standard cycle message.
+				//       cycle is now handled explicitly in loader
+			}
+			paths[i] = imp.ImportPath
+
+			inst.addImport(imp)
+			if imp.Incomplete {
+				inst.Incomplete = true
+			}
+		}
+	}
+
+	inst.ImportPaths = paths
+	inst.ImportPos = imported
+
+	// Build full dependencies
+	deps := make(map[string]*Instance)
+	var q []*Instance
+	q = append(q, inst.Imports...)
+	for i := 0; i < len(q); i++ {
+		p1 := q[i]
+		path := p1.ImportPath
+		// The same import path could produce an error or not,
+		// depending on what tries to import it.
+		// Prefer to record entries with errors, so we can report them.
+		// p0 := deps[path]
+		// if err0, err1 := lastError(p0), lastError(p1); p0 == nil || err1 != nil && (err0 == nil || len(err0.ImportStack) > len(err1.ImportStack)) {
+		// 	deps[path] = p1
+		// 	for _, p2 := range p1.Imports {
+		// 		if deps[p2.ImportPath] != p2 {
+		// 			q = append(q, p2)
+		// 		}
+		// 	}
+		// }
+		if _, ok := deps[path]; !ok {
+			deps[path] = p1
+		}
+	}
+	inst.Deps = make([]string, 0, len(deps))
+	for dep := range deps {
+		inst.Deps = append(inst.Deps, dep)
+	}
+	sort.Strings(inst.Deps)
+
+	for _, dep := range inst.Deps {
+		p1 := deps[dep]
+		if p1 == nil {
+			panic("impossible: missing entry in package cache for " + dep + " imported by " + inst.ImportPath)
+		}
+		if p1.Err != nil {
+			inst.DepsErrors = append(inst.DepsErrors, p1.Err)
+		}
+	}
+
+	return nil
+}
diff --git a/cue/build/instance.go b/cue/build/instance.go
new file mode 100644
index 0000000..966d9ea
--- /dev/null
+++ b/cue/build/instance.go
@@ -0,0 +1,240 @@
+// Copyright 2018 The 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 build
+
+import (
+	"fmt"
+	pathpkg "path"
+	"path/filepath"
+	"strings"
+	"unicode"
+
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/errors"
+	"cuelang.org/go/cue/parser"
+	"cuelang.org/go/cue/token"
+)
+
+// An Instance describes the collection of files, and its imports, necessary
+// to build a CUE instance.
+//
+// A typical way to create an Instance is to use the loader package.
+type Instance struct {
+	ctxt *Context
+
+	// Files contains the AST for all files part of this instance.
+	Files []*ast.File
+
+	loadFunc LoadFunc
+	done     bool
+
+	// Scope is another instance that may be used to resolve any unresolved
+	// reference of this instance. For instance, tool and test instances
+	// may refer to top-level fields in their package scope.
+	Scope *Instance
+
+	// PkgName is the name specified in the package clause.
+	PkgName string
+	hasName bool
+
+	// ImportPath returns the unique path to identify an imported instance.
+	//
+	// Instances created with NewInstance do not have an import path.
+	ImportPath string
+
+	// Imports lists the instances of all direct imports of this instance.
+	Imports []*Instance
+
+	// The Err for loading this package or nil on success. This does not
+	// include any errors of dependencies. Incomplete will be set if there
+	// were any errors in dependencies.
+	Err error
+
+	// Incomplete reports whether any dependencies had an error.
+	Incomplete bool
+
+	parent *Instance // TODO: for cycle detection
+
+	// The following fields are for informative purposes and are not used by
+	// the cue package to create an instance.
+
+	// ImportComment is the path in the import comment on the package statement.
+	ImportComment string
+
+	// DisplayPath is a user-friendly version of the package or import path.
+	DisplayPath string
+
+	// Dir is the package directory. Note that a package may also include files
+	// from ancestor directories, up to the module file.
+	Dir string
+
+	Root string // module root directory ("" if unknown)
+
+	// AllTags are the build tags that can influence file selection in this
+	// directory.
+	AllTags []string
+
+	Standard    bool // Is a builtin package
+	Local       bool
+	localPrefix string
+
+	// Relative to Dir
+	CUEFiles        []string // .cue source files
+	DataFiles       []string // recognized data files (.json, .yaml, etc.)
+	TestCUEFiles    []string // .cue test files (_test.cue)
+	ToolCUEFiles    []string // .cue tool files (_tool.cue)
+	IgnoredCUEFiles []string // .cue source files ignored for this build
+	InvalidCUEFiles []string // .cue source files with detected problems (parse error, wrong package name, and so on)
+
+	// Dependencies
+	ImportPaths []string
+	ImportPos   map[string][]token.Position // line information for Imports
+
+	Deps       []string
+	DepsErrors []error
+	Match      []string
+}
+
+// Abs converts relative path used in the one of the file fields to an
+// absolute one.
+func (inst *Instance) Abs(path string) string {
+	if filepath.IsAbs(path) {
+		return path
+	}
+	return filepath.Join(inst.Root, path)
+}
+
+func (inst *Instance) chkErr(err error) error {
+	if err != nil {
+		inst.ReportError(err)
+	}
+	return err
+}
+
+func (inst *Instance) setPkg(pkg string) bool {
+	if !inst.hasName {
+		inst.hasName = true
+		inst.PkgName = pkg
+		return true
+	}
+	return false
+}
+
+// ReportError reports an error processing this instance.
+func (inst *Instance) ReportError(err error) {
+	if inst.Err == nil {
+		inst.Err = err
+	}
+}
+
+func (inst *Instance) errorf(pos token.Pos, format string, args ...interface{}) error {
+	return inst.chkErr(errors.E(inst.ctxt.Pos(pos), fmt.Sprintf(format, args...)))
+}
+
+// Context defines the build context for this instance. All files defined
+// in Syntax as well as all imported instances must be created using the
+// same build context.
+func (inst *Instance) Context() *Context {
+	return inst.ctxt
+}
+
+// LookupImport defines a mapping from an ImportSpec's ImportPath to Instance.
+func (inst *Instance) LookupImport(path string) *Instance {
+	path = inst.expandPath(path)
+	for _, inst := range inst.Imports {
+		if inst.ImportPath == path {
+			return inst
+		}
+	}
+	return nil
+}
+
+func (inst *Instance) addImport(imp *Instance) {
+	for _, inst := range inst.Imports {
+		if inst.ImportPath == imp.ImportPath {
+			if inst != imp {
+				panic("import added multiple times with different instances")
+			}
+			return
+		}
+	}
+	inst.Imports = append(inst.Imports, imp)
+}
+
+// AddFile adds the file with the given name to the list of files for this
+// instance. The file may be loaded from the cache of the instance's context.
+// It does not process the file's imports. The package name of the file must
+// match the package name of the instance.
+func (inst *Instance) AddFile(filename string, src interface{}) error {
+	c := inst.ctxt
+	file, err := parser.ParseFile(c.FileSet(), filename, src, c.parseOptions...)
+	if err == nil {
+		err = inst.addSyntax(file)
+	}
+	return inst.chkErr(err)
+}
+
+// addSyntax adds the given file to list of files for this instance. The package
+// name of the file must match the package name of the instance.
+func (inst *Instance) addSyntax(file *ast.File) error {
+	pkg := ""
+	pos := file.Pos()
+	if file.Name != nil {
+		pkg = file.Name.Name
+		pos = file.Name.Pos()
+	}
+	if !inst.setPkg(pkg) && pkg != inst.PkgName {
+		return inst.errorf(pos,
+			"package name %q conflicts with previous package name %q",
+			pkg, inst.PkgName)
+	}
+	inst.Files = append(inst.Files, file)
+	return nil
+}
+
+func (inst *Instance) expandPath(path string) string {
+	isLocal := IsLocalImport(path)
+	if isLocal {
+		path = dirToImportPath(filepath.Join(inst.Dir, path))
+	}
+	return path
+}
+
+// dirToImportPath returns the pseudo-import path we use for a package
+// outside the CUE path. It begins with _/ and then contains the full path
+// to the directory. If the package lives in c:\home\gopher\my\pkg then
+// the pseudo-import path is _/c_/home/gopher/my/pkg.
+// Using a pseudo-import path like this makes the ./ imports no longer
+// a special case, so that all the code to deal with ordinary imports works
+// automatically.
+func dirToImportPath(dir string) string {
+	return pathpkg.Join("_", strings.Map(makeImportValid, filepath.ToSlash(dir)))
+}
+
+func makeImportValid(r rune) rune {
+	// Should match Go spec, compilers, and ../../go/parser/parser.go:/isValidImport.
+	const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"
+	if !unicode.IsGraphic(r) || unicode.IsSpace(r) || strings.ContainsRune(illegalChars, r) {
+		return '_'
+	}
+	return r
+}
+
+// IsLocalImport reports whether the import path is
+// a local import path, like ".", "..", "./foo", or "../foo".
+func IsLocalImport(path string) bool {
+	return path == "." || path == ".." ||
+		strings.HasPrefix(path, "./") || strings.HasPrefix(path, "../")
+}