1// Copyright 2020 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package export
16
17import (
18 "fmt"
19 "math/rand/v2"
20 "slices"
21
22 "cuelang.org/go/cue/ast"
23 "cuelang.org/go/cue/ast/astutil"
24 "cuelang.org/go/cue/errors"
25 "cuelang.org/go/cue/token"
26 "cuelang.org/go/internal"
27 "cuelang.org/go/internal/core/adt"
28 "cuelang.org/go/internal/core/eval"
29 "cuelang.org/go/internal/core/walk"
30)
31
32const debug = false
33
34type Profile struct {
35 Simplify bool
36
37 // Final reports incomplete errors as errors.
38 Final bool
39
40 // TakeDefaults is used in Value mode to drop non-default values.
41 TakeDefaults bool
42
43 ShowOptional bool
44 ShowDefinitions bool
45
46 // ShowHidden forces the inclusion of hidden fields when these would
47 // otherwise be omitted. Only hidden fields from the current package are
48 // included.
49 ShowHidden bool
50 ShowDocs bool
51 ShowAttributes bool
52
53 // ShowErrors treats errors as values and will not percolate errors up.
54 //
55 // TODO: convert this option to an error level instead, showing only
56 // errors below a certain severity.
57 ShowErrors bool
58
59 // Use unevaluated conjuncts for these error types
60 // IgnoreRecursive
61
62 // SelfContained exports a schema such that it does not rely on any imports.
63 SelfContained bool
64
65 // Fragment disables printing a value as self contained. To successfully
66 // parse a fragment, the compiler needs to be given a scope with the value
67 // from which the fragment was extracted.
68 Fragment bool
69
70 // AddPackage causes a package clause to be added.
71 AddPackage bool
72
73 // InlineImports expands references to non-builtin packages.
74 InlineImports bool
75
76 // ExpandReferences causes all references to be expanded inline. This
77 // disables the ability to prevent billion laughs attacks, so use with care.
78 ExpandReferences bool
79}
80
81var Simplified = &Profile{
82 Simplify: true,
83 ShowDocs: true,
84}
85
86var Final = &Profile{
87 Simplify: true,
88 TakeDefaults: true,
89 Final: true,
90}
91
92var Raw = &Profile{
93 ShowOptional: true,
94 ShowDefinitions: true,
95 ShowHidden: true,
96 ShowDocs: true,
97 AddPackage: true,
98}
99
100var All = &Profile{
101 Simplify: true,
102 ShowOptional: true,
103 ShowDefinitions: true,
104 ShowHidden: true,
105 ShowDocs: true,
106 ShowAttributes: true,
107 AddPackage: true,
108}
109
110// Concrete
111
112// Def exports v as a definition.
113// It resolves references that point outside any of the vertices in v.
114func Def(r adt.Runtime, pkgID string, v *adt.Vertex) (*ast.File, errors.Error) {
115 return All.Def(r, pkgID, v)
116}
117
118// Def exports v as a definition.
119// It resolves references that point outside any of the vertices in v.
120func (p *Profile) Def(r adt.Runtime, pkgID string, v *adt.Vertex) (f *ast.File, err errors.Error) {
121 e := newExporter(p, r, pkgID, v)
122 e.initPivot(v)
123
124 isDef := v.IsRecursivelyClosed()
125 if isDef {
126 e.inDefinition++
127 }
128
129 expr := e.expr(nil, v)
130
131 switch isDef {
132 case true:
133 e.inDefinition--
134
135 // This eliminates the need to wrap in _#def in the most common cases,
136 // while ensuring only one level of _#def wrapping is ever used.
137 if st, ok := expr.(*ast.StructLit); ok {
138 for _, elem := range st.Elts {
139 if d, ok := elem.(*ast.EmbedDecl); ok {
140 if isDefinitionReference(d.Expr) {
141 return e.finalize(v, expr)
142 }
143 }
144 }
145 }
146
147 // TODO: embed an empty definition instead once we verify that this
148 // preserves semantics.
149 if v.Kind() == adt.StructKind && !p.Fragment {
150 expr = ast.NewStruct(
151 ast.Embed(ast.NewIdent("_#def")),
152 ast.NewIdent("_#def"), expr,
153 )
154 }
155 }
156
157 return e.finalize(v, expr)
158}
159
160func isDefinitionReference(x ast.Expr) bool {
161 switch x := x.(type) {
162 case *ast.Ident:
163 if internal.IsDef(x.Name) {
164 return true
165 }
166 case *ast.SelectorExpr:
167 if internal.IsDefinition(x.Sel) {
168 return true
169 }
170 return isDefinitionReference(x.X)
171 case *ast.IndexExpr:
172 return isDefinitionReference(x.X)
173 }
174 return false
175}
176
177// Expr exports the given unevaluated expression (schema mode).
178// It does not resolve references that point outside the given expression.
179func Expr(r adt.Runtime, pkgID string, n adt.Expr) (ast.Expr, errors.Error) {
180 return Simplified.Expr(r, pkgID, n)
181}
182
183// Expr exports the given unevaluated expression (schema mode).
184// It does not resolve references that point outside the given expression.
185func (p *Profile) Expr(r adt.Runtime, pkgID string, n adt.Expr) (ast.Expr, errors.Error) {
186 e := newExporter(p, r, pkgID, nil)
187
188 return e.expr(nil, n), nil
189}
190
191func (e *exporter) toFile(v *adt.Vertex, x ast.Expr) *ast.File {
192 fout := &ast.File{}
193
194 if e.cfg.AddPackage {
195 pkgName := ""
196 pkg := &ast.Package{
197 // prevent the file comment from attaching to pkg when there is no pkg comment
198 PackagePos: token.NoPos.WithRel(token.NewSection),
199 }
200 for c := range v.LeafConjuncts() {
201 f, _ := c.Source().(*ast.File)
202 if f == nil {
203 continue
204 }
205
206 if name := f.PackageName(); name != "" {
207 pkgName = name
208 }
209
210 if e.cfg.ShowDocs {
211 pkgComments, fileComments := internal.FileComments(f)
212
213 for _, c := range pkgComments {
214 // add a newline between previous file comment and the pkg comments
215 c.List[0].Slash = c.List[0].Slash.WithRel(token.NewSection)
216 ast.AddComment(pkg, c)
217 }
218 for _, c := range fileComments {
219 ast.AddComment(fout, c)
220 }
221 }
222 }
223
224 if pkgName != "" {
225 pkg.Name = ast.NewIdent(pkgName)
226 fout.Decls = append(fout.Decls, pkg)
227 ast.SetComments(pkg, mergeDocs(ast.Comments(pkg)))
228 } else {
229 for _, c := range ast.Comments(fout) {
230 ast.AddComment(pkg, c)
231 }
232 ast.SetComments(fout, mergeDocs(ast.Comments(pkg)))
233 }
234 }
235
236 switch st := x.(type) {
237 case nil:
238 panic("null input")
239
240 case *ast.StructLit:
241 fout.Decls = append(fout.Decls, st.Elts...)
242
243 default:
244 fout.Decls = append(fout.Decls, &ast.EmbedDecl{Expr: x})
245 }
246
247 return fout
248}
249
250// mergeDocs merges multiple doc comments into one single doc comment.
251func mergeDocs(comments []*ast.CommentGroup) []*ast.CommentGroup {
252 if len(comments) <= 1 || !hasDocComment(comments) {
253 return comments
254 }
255
256 comments1 := make([]*ast.CommentGroup, 0, len(comments))
257 comments1 = append(comments1, nil)
258 var docComment *ast.CommentGroup
259 for _, c := range comments {
260 switch {
261 case !c.Doc:
262 comments1 = append(comments1, c)
263 case docComment == nil:
264 docComment = c
265 default:
266 docComment.List = append(slices.Clip(docComment.List), &ast.Comment{Text: "//"})
267 docComment.List = append(docComment.List, c.List...)
268 }
269 }
270 comments1[0] = docComment
271 return comments1
272}
273
274func hasDocComment(comments []*ast.CommentGroup) bool {
275 for _, c := range comments {
276 if c.Doc {
277 return true
278 }
279 }
280 return false
281}
282
283// Vertex exports evaluated values (data mode).
284// It resolves incomplete references that point outside the current context.
285func Vertex(r adt.Runtime, pkgID string, n *adt.Vertex) (*ast.File, errors.Error) {
286 return Simplified.Vertex(r, pkgID, n)
287}
288
289// Vertex exports evaluated values (data mode).
290// It resolves incomplete references that point outside the current context.
291func (p *Profile) Vertex(r adt.Runtime, pkgID string, n *adt.Vertex) (f *ast.File, err errors.Error) {
292 e := newExporter(p, r, pkgID, n)
293 e.initPivot(n)
294
295 v := e.value(n, n.Conjuncts...)
296 return e.finalize(n, v)
297}
298
299// Value exports evaluated values (data mode).
300// It does not resolve references that point outside the given Value.
301func Value(r adt.Runtime, pkgID string, n adt.Value) (ast.Expr, errors.Error) {
302 return Simplified.Value(r, pkgID, n)
303}
304
305// Value exports evaluated values (data mode).
306//
307// It does not resolve references that point outside the given Value.
308//
309// TODO: Should take context.
310func (p *Profile) Value(r adt.Runtime, pkgID string, n adt.Value) (ast.Expr, errors.Error) {
311 e := newExporter(p, r, pkgID, n)
312 v := e.value(n)
313 return v, e.errs
314}
315
316type exporter struct {
317 cfg *Profile // Make value todo
318 errs errors.Error
319
320 ctx *adt.OpContext
321
322 index adt.StringIndexer
323 rand *rand.Rand
324
325 // For resolving references.
326 stack []frame
327
328 inDefinition int // for close() wrapping.
329 inExpression int // for inlining decisions.
330
331 // hidden label handling
332 pkgID string
333 // pkgHash is used when mangling hidden identifiers of packages that are
334 // inlined.
335 pkgHash map[string]string
336
337 // If a used feature maps to an expression, it means it is assigned to a
338 // unique let expression.
339 usedFeature map[adt.Feature]adt.Expr
340 labelAlias map[adt.Expr]adt.Feature
341 valueAlias map[*ast.Alias]*ast.Alias
342 // fieldAlias is used to track original alias names of regular fields.
343 fieldAlias map[*ast.Field]fieldAndScope
344 letAlias map[*ast.LetClause]*ast.LetClause
345 references map[*adt.Vertex]*referenceInfo
346
347 pivotter *pivotter
348}
349
350type fieldAndScope struct {
351 field *ast.Field
352 scope ast.Node // StructLit or File
353}
354
355// referenceInfo is used to track which Field.Value fields should be linked
356// to Ident.Node fields. The Node field is used by astutil.Resolve to mark
357// the value in the AST to which the respective identifier points.
358// astutil.Sanitize, in turn, uses this information to determine whether
359// a reference is shadowed and apply fixes accordingly.
360type referenceInfo struct {
361 field *ast.Field
362 references []*ast.Ident
363}
364
365// linkField reports the Field that represents certain Vertex in the generated
366// output. The Node fields for any references (*ast.Ident) that were already
367// recorded as pointed to this vertex are updated accordingly.
368func (e *exporter) linkField(v *adt.Vertex, f *ast.Field) {
369 if v == nil {
370 return
371 }
372 refs := e.references[v]
373 if refs == nil {
374 // TODO(perf): do a first sweep to only mark referenced arcs or keep
375 // track of that information elsewhere.
376 e.references[v] = &referenceInfo{field: f}
377 return
378 }
379 for _, r := range refs.references {
380 r.Node = f.Value
381 }
382 refs.references = refs.references[:0]
383}
384
385// linkIdentifier reports the Vertex to which indent points. Once the ast.Field
386// for a corresponding Vertex is known, it is linked to ident.
387func (e *exporter) linkIdentifier(v *adt.Vertex, ident *ast.Ident) {
388 refs := e.references[v]
389 if refs == nil {
390 refs = &referenceInfo{}
391 e.references[v] = refs
392 }
393 if refs.field == nil {
394 refs.references = append(refs.references, ident)
395 return
396 }
397 ident.Node = refs.field.Value
398}
399
400// newExporter creates and initializes an exporter.
401func newExporter(p *Profile, r adt.Runtime, pkgID string, v adt.Value) *exporter {
402 n, _ := v.(*adt.Vertex)
403 e := &exporter{
404 cfg: p,
405 ctx: eval.NewContext(r, n),
406 index: r,
407 pkgID: pkgID,
408
409 references: map[*adt.Vertex]*referenceInfo{},
410 }
411
412 e.markUsedFeatures(v)
413
414 return e
415}
416
417// initPivot initializes the pivotter to allow aligning a configuration around
418// a new root, if needed.
419func (e *exporter) initPivot(n *adt.Vertex) {
420 switch {
421 case e.cfg.SelfContained, e.cfg.InlineImports:
422 // Explicitly enabled.
423 case n.Parent == nil, e.cfg.Fragment, e.cfg.ExpandReferences:
424 return
425 }
426 e.initPivotter(n)
427}
428
429// finalize finalizes the result of an export. It is only needed for use cases
430// that require conversion to a File, Sanitization, and self containment.
431func (e *exporter) finalize(n *adt.Vertex, v ast.Expr) (f *ast.File, err errors.Error) {
432 f = e.toFile(n, v)
433
434 e.completePivot(f)
435
436 if err := astutil.Sanitize(f); err != nil {
437 err := errors.Promote(err, "export")
438 return f, errors.Append(e.errs, err)
439 }
440
441 return f, nil
442}
443
444func (e *exporter) markUsedFeatures(x adt.Expr) {
445 e.usedFeature = make(map[adt.Feature]adt.Expr)
446
447 w := &walk.Visitor{}
448 w.Before = func(n adt.Node) bool {
449 switch x := n.(type) {
450 case *adt.Vertex:
451 if !x.IsData() {
452 for c := range x.LeafConjuncts() {
453 w.Elem(c.Elem())
454 }
455 }
456
457 case *adt.DynamicReference:
458 if e.labelAlias == nil {
459 e.labelAlias = make(map[adt.Expr]adt.Feature)
460 }
461 // TODO: add preferred label.
462 e.labelAlias[x.Label] = adt.InvalidLabel
463
464 case *adt.LabelReference:
465 }
466 return true
467 }
468
469 w.Feature = func(f adt.Feature, src adt.Node) {
470 _, ok := e.usedFeature[f]
471
472 switch x := src.(type) {
473 case *adt.LetReference:
474 if !ok {
475 e.usedFeature[f] = x.X
476 }
477
478 default:
479 e.usedFeature[f] = nil
480 }
481 }
482
483 w.Elem(x)
484}
485
486func (e *exporter) getFieldAlias(f *ast.Field, name string) string {
487 a, ok := f.Label.(*ast.Alias)
488 if !ok {
489 a = &ast.Alias{
490 Ident: ast.NewIdent(e.uniqueAlias(name)),
491 Expr: f.Label.(ast.Expr),
492 }
493 f.Label = a
494 }
495 return a.Ident.Name
496}
497
498func setFieldAlias(f *ast.Field, name string) {
499 if _, ok := f.Label.(*ast.Alias); !ok {
500 x := f.Label.(ast.Expr)
501 f.Label = &ast.Alias{
502 Ident: ast.NewIdent(name),
503 Expr: x,
504 }
505 ast.SetComments(f.Label, ast.Comments(x))
506 ast.SetComments(x, nil)
507 // TODO: move position information.
508 }
509}
510
511func (e *exporter) markLets(n ast.Node, scope *ast.StructLit) {
512 if n == nil {
513 return
514 }
515 ast.Walk(n, func(n ast.Node) bool {
516 switch v := n.(type) {
517 case *ast.StructLit:
518 e.markLetDecls(v.Elts, scope)
519 case *ast.File:
520 e.markLetDecls(v.Decls, scope)
521 // TODO: return true here and false for everything else?
522
523 case *ast.Field,
524 *ast.LetClause,
525 *ast.IfClause,
526 *ast.ForClause,
527 *ast.Comprehension:
528 return false
529 }
530 return true
531 }, nil)
532}
533
534func (e *exporter) markLetDecls(decls []ast.Decl, scope *ast.StructLit) {
535 for _, d := range decls {
536 switch x := d.(type) {
537 case *ast.Field:
538 e.prepareAliasedField(x, scope)
539 case *ast.LetClause:
540 e.markLetAlias(x)
541 }
542 }
543}
544
545// prepareAliasField creates an aliased ast.Field. It is done so before
546// recursively processing any of the fields so that a processed field that
547// occurs earlier in a struct can already refer to it.
548//
549// It is assumed that the same alias names can be used. We rely on Sanitize
550// to do any renaming of aliases in case of shadowing.
551func (e *exporter) prepareAliasedField(f *ast.Field, scope ast.Node) {
552 if _, ok := e.fieldAlias[f]; ok {
553 return
554 }
555
556 alias, ok := f.Label.(*ast.Alias)
557 if !ok {
558 return // not aliased
559 }
560 field := &ast.Field{
561 Label: &ast.Alias{
562 Ident: ast.NewIdent(alias.Ident.Name),
563 Expr: alias.Expr,
564 },
565 }
566
567 if e.fieldAlias == nil {
568 e.fieldAlias = make(map[*ast.Field]fieldAndScope)
569 }
570
571 e.fieldAlias[f] = fieldAndScope{field: field, scope: scope}
572}
573
574func (e *exporter) getFixedField(f *adt.Field) *ast.Field {
575 if f.Src != nil {
576 if entry, ok := e.fieldAlias[f.Src]; ok {
577 return entry.field
578 }
579 }
580 return &ast.Field{
581 Label: e.stringLabel(f.Label),
582 }
583}
584
585// markLetAlias inserts an uninitialized let clause into the current scope.
586// It gets initialized upon first usage.
587func (e *exporter) markLetAlias(x *ast.LetClause) {
588 // The created let clause is initialized upon first usage, and removed
589 // later if never referenced.
590 let := &ast.LetClause{}
591
592 if e.letAlias == nil {
593 e.letAlias = make(map[*ast.LetClause]*ast.LetClause)
594 }
595 e.letAlias[x] = let
596
597 scope := e.top().scope
598 scope.Elts = append(scope.Elts, let)
599}
600
601// In value mode, lets are only used if there wasn't an error.
602func filterUnusedLets(s *ast.StructLit) {
603 s.Elts = slices.DeleteFunc(s.Elts, func(d ast.Decl) bool {
604 let, ok := d.(*ast.LetClause)
605 return ok && let.Expr == nil
606 })
607}
608
609// resolveLet actually parses the let expression.
610// If there was no recorded let expression, it expands the expression in place.
611func (e *exporter) resolveLet(env *adt.Environment, x *adt.LetReference) ast.Expr {
612 letClause, _ := x.Src.Node.(*ast.LetClause)
613 let := e.letAlias[letClause]
614
615 switch {
616 case let == nil:
617 ref, _ := e.ctx.Lookup(env, x)
618 if ref == nil {
619 // This can happen if x.X does not resolve to a valid value. At this
620 // point we will not get a valid configuration.
621
622 // TODO: get rid of the use of x.X.
623 // str := x.Label.IdentString(e.ctx)
624 // ident := ast.NewIdent(str)
625 // return ident
626
627 return e.expr(env, x.X)
628 }
629 c, _ := ref.SingleConjunct()
630 return e.expr(c.EnvExpr())
631
632 case let.Expr == nil:
633 label := e.uniqueLetIdent(x.Label, x.X)
634
635 let.Ident = e.ident(label)
636 let.Expr = e.expr(env, x.X)
637 }
638
639 ident := ast.NewIdent(let.Ident.Name)
640 ident.Node = let
641 // TODO: set scope?
642 return ident
643}
644
645func (e *exporter) uniqueLetIdent(f adt.Feature, x adt.Expr) adt.Feature {
646 if e.usedFeature[f] == x {
647 return f
648 }
649
650 f, _ = e.uniqueFeature(f.IdentString(e.ctx))
651 e.usedFeature[f] = x
652 return f
653}
654
655func (e *exporter) uniqueAlias(name string) string {
656 f := adt.MakeIdentLabel(e.ctx, name, "")
657
658 if _, ok := e.usedFeature[f]; !ok {
659 e.usedFeature[f] = nil
660 return name
661 }
662
663 _, name = e.uniqueFeature(f.IdentString(e.ctx))
664 return name
665}
666
667// A featureSet implements a set of Features. It only supports testing
668// whether a given string is available as a Feature.
669type featureSet interface {
670 // intn returns a pseudo-random integer in [0..n).
671 intn(n int) int
672
673 // makeFeature converts s to f if it is available.
674 makeFeature(s string) (f adt.Feature, ok bool)
675}
676
677func (e *exporter) intn(n int) int {
678 return e.rand.IntN(n)
679}
680
681func (e *exporter) makeFeature(s string) (f adt.Feature, ok bool) {
682 f = adt.MakeIdentLabel(e.ctx, s, "")
683 _, exists := e.usedFeature[f]
684 if !exists {
685 e.usedFeature[f] = nil
686 }
687 return f, !exists
688}
689
690// uniqueFeature returns a name for an identifier that uniquely identifies
691// the given expression. If the preferred name is already taken, a new globally
692// unique name of the form base_N ... base_NNNNNNNNNNNNNN is generated.
693//
694// It prefers short extensions over large ones, while ensuring the likelihood of
695// fast termination is high. There are at least two digits to make it visually
696// clearer this concerns a generated number.
697func (e *exporter) uniqueFeature(base string) (f adt.Feature, name string) {
698 if e.rand == nil {
699 e.rand = rand.New(rand.NewPCG(123, 456)) // ensure determinism between runs
700 }
701 return findUnique(e, base)
702}
703
704func findUnique(set featureSet, base string) (f adt.Feature, name string) {
705 if f, ok := set.makeFeature(base); ok {
706 return f, base
707 }
708
709 // Try the first few numbers in sequence.
710 for i := 1; i < 5; i++ {
711 name := fmt.Sprintf("%s_%01X", base, i)
712 if f, ok := set.makeFeature(name); ok {
713 return f, name
714 }
715 }
716
717 const mask = 0xff_ffff_ffff_ffff // max bits; stay clear of int64 overflow
718 const shift = 4 // rate of growth
719 digits := 1
720 for n := int64(0x10); ; n = mask&((n<<shift)-1) + 1 {
721 num := set.intn(int(n)-1) + 1
722 name := fmt.Sprintf("%[1]s_%0[2]*[3]X", base, digits, num)
723 if f, ok := set.makeFeature(name); ok {
724 return f, name
725 }
726 digits++
727 }
728}
729
730type frame struct {
731 node *adt.Vertex
732
733 scope *ast.StructLit
734
735 docSources []adt.Conjunct
736
737 // For resolving pattern constraints fields labels
738 field *ast.Field
739 labelExpr ast.Expr
740
741 dynamicFields []*entry
742
743 // for off-by-one handling
744 upCount int32
745
746 // labeled fields
747 fields map[adt.Feature]entry
748
749 // field to new field
750 mapped map[adt.Node]ast.Node
751}
752
753type entry struct {
754 alias string
755 field *ast.Field
756 node ast.Node // How to reference. See astutil.Resolve
757 references []*ast.Ident
758}
759
760func (e *exporter) addField(label adt.Feature, f *ast.Field, n ast.Node) {
761 frame := e.top()
762 entry := frame.fields[label]
763 entry.field = f
764 entry.node = n
765 frame.fields[label] = entry
766}
767
768func (e *exporter) addEmbed(x ast.Expr) {
769 frame := e.top()
770 frame.scope.Elts = append(frame.scope.Elts, x)
771}
772
773func (e *exporter) pushFrame(src *adt.Vertex, conjuncts []adt.Conjunct) (s *ast.StructLit, saved []frame) {
774 saved = e.stack
775 s = &ast.StructLit{}
776 e.stack = append(e.stack, frame{
777 node: src,
778 scope: s,
779 mapped: map[adt.Node]ast.Node{},
780 fields: map[adt.Feature]entry{},
781 docSources: conjuncts,
782 })
783 return s, saved
784}
785
786func (e *exporter) popFrame(saved []frame) {
787 top := e.stack[len(e.stack)-1]
788
789 for _, f := range top.fields {
790 node := f.node
791 if f.alias != "" && f.field != nil {
792 setFieldAlias(f.field, f.alias)
793 node = f.field
794 }
795 if node != nil {
796 for _, r := range f.references {
797 r.Node = node
798 }
799 }
800 }
801
802 e.stack = saved
803}
804
805func (e *exporter) top() *frame {
806 return &(e.stack[len(e.stack)-1])
807}
808
809func (e *exporter) node() *adt.Vertex {
810 if len(e.stack) == 0 {
811 return empty
812 }
813 n := e.stack[len(e.stack)-1].node
814 if n == nil {
815 return empty
816 }
817 return n
818}
819
820func (e *exporter) frame(upCount int32) *frame {
821 for i := len(e.stack) - 1; i >= 0; i-- {
822 f := &(e.stack[i])
823 if upCount <= (f.upCount - 1) {
824 return f
825 }
826 upCount -= f.upCount
827 }
828 if debug {
829 // This may be valid when exporting incomplete references. These are
830 // not yet handled though, so find a way to catch them when debugging
831 // printing of values that are supposed to be complete.
832 panic("unreachable reference")
833 }
834
835 return &frame{}
836}
837
838func (e *exporter) setDocs(x adt.Node) {
839 f := e.stack[len(e.stack)-1]
840 f.docSources = []adt.Conjunct{adt.MakeRootConjunct(nil, x)}
841 e.stack[len(e.stack)-1] = f
842}