internal/core/export: initial commit

Change-Id: Ib388135cc653bdb21d8bc72bea0983df429ec9c2
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6504
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/internal/core/export/extract.go b/internal/core/export/extract.go
new file mode 100644
index 0000000..c3b11ef
--- /dev/null
+++ b/internal/core/export/extract.go
@@ -0,0 +1,149 @@
+// 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 (
+	"cuelang.org/go/cue/ast"
+	"cuelang.org/go/cue/token"
+	"cuelang.org/go/internal/core/adt"
+)
+
+// ExtractDoc collects documentation strings for a field.
+//
+// Comments are attached to a field with a field shorthand belong to the
+// child node. So in the following the comment is attached to field bar.
+//
+//     // comment
+//     foo: bar: 2
+//
+func ExtractDoc(v *adt.Vertex) (docs []*ast.CommentGroup) {
+	fields := []*ast.Field{}
+
+	// Collect docs directly related to this Vertex.
+	for _, x := range v.Conjuncts {
+		f, ok := x.Source().(*ast.Field)
+		if !ok || hasShorthandValue(f) {
+			continue
+		}
+
+		fields = append(fields, f)
+		for _, cg := range f.Comments() {
+			if !containsDoc(docs, cg) && cg.Doc {
+				docs = append(docs, cg)
+			}
+		}
+	}
+
+	// Collect docs from parent scopes in collapsed fields.
+	for p := v.Parent; p != nil; p = p.Parent {
+
+		newFields := []*ast.Field{}
+
+		for _, x := range p.Conjuncts {
+			f, ok := x.Source().(*ast.Field)
+			if !ok || !hasShorthandValue(f) {
+				continue
+			}
+
+			nested := nestedField(f)
+			for _, child := range fields {
+				if nested == child {
+					newFields = append(newFields, f)
+					for _, cg := range f.Comments() {
+						if !containsDoc(docs, cg) && cg.Doc {
+							docs = append(docs, cg)
+						}
+					}
+				}
+			}
+		}
+
+		fields = newFields
+	}
+	return docs
+}
+
+// hasShorthandValue reports whether this field has a struct value that will
+// be rendered as a shorthand, for instance:
+//
+//     f: g: 2
+//
+func hasShorthandValue(f *ast.Field) bool {
+	if f = nestedField(f); f == nil {
+		return false
+	}
+
+	// Not a regular field, but shorthand field.
+	// TODO: Should we return here? For now mimic old implementation.
+	if _, _, err := ast.LabelName(f.Label); err != nil {
+		return false
+	}
+
+	return true
+}
+
+// nestedField returns the child field of a field shorthand.
+func nestedField(f *ast.Field) *ast.Field {
+	s, _ := f.Value.(*ast.StructLit)
+	if s == nil ||
+		len(s.Elts) != 1 ||
+		s.Lbrace != token.NoPos ||
+		s.Rbrace != token.NoPos {
+		return nil
+	}
+
+	f, _ = s.Elts[0].(*ast.Field)
+	return f
+}
+
+func containsDoc(a []*ast.CommentGroup, cg *ast.CommentGroup) bool {
+	for _, c := range a {
+		if c == cg {
+			return true
+		}
+	}
+
+	for _, c := range a {
+		if c.Text() == cg.Text() {
+			return true
+		}
+	}
+
+	return false
+}
+
+func ExtractFieldAttrs(a []adt.Conjunct) (attrs []*ast.Attribute) {
+	for _, x := range a {
+		f, ok := x.Source().(*ast.Field)
+		if !ok {
+			continue
+		}
+		for _, a := range f.Attrs {
+			if !containsAttr(attrs, a) {
+				attrs = append(attrs, a)
+			}
+		}
+	}
+	return attrs
+}
+
+func containsAttr(a []*ast.Attribute, x *ast.Attribute) bool {
+	for _, e := range a {
+		if e.Text == x.Text {
+			return true
+		}
+	}
+	return false
+}