@@ -64,6 +64,15 @@ type decoder struct {
64
64
65
65
// forceNewline ensures that the next position will be on a new line.
66
66
forceNewline bool
67
+
68
+ // anchorFields contains the anchors that are gathered as we walk the YAML nodes.
69
+ // these are only added to the AST when we're done processing the whole document.
70
+ anchorFields []ast.Field
71
+ // anchorNames map anchor nodes to their names.
72
+ anchorNames map [* yaml.Node ]string
73
+ // anchorTakenNames keeps track of anchor names that have been taken.
74
+ // It is used to ensure unique anchor names.
75
+ anchorTakenNames map [string ]struct {}
67
76
}
68
77
69
78
// TODO(mvdan): this can be io.Reader really, except that token.Pos is offset-based,
@@ -83,9 +92,11 @@ func NewDecoder(filename string, b []byte) *decoder {
83
92
tokFile := token .NewFile (filename , 0 , len (b )+ 1 )
84
93
tokFile .SetLinesForContent (b )
85
94
return & decoder {
86
- tokFile : tokFile ,
87
- tokLines : append (tokFile .Lines (), len (b )),
88
- yamlDecoder : * yaml .NewDecoder (bytes .NewReader (b )),
95
+ tokFile : tokFile ,
96
+ tokLines : append (tokFile .Lines (), len (b )),
97
+ yamlDecoder : * yaml .NewDecoder (bytes .NewReader (b )),
98
+ anchorNames : make (map [* yaml.Node ]string ),
99
+ anchorTakenNames : make (map [string ]struct {}),
89
100
}
90
101
}
91
102
@@ -176,24 +187,35 @@ func Unmarshal(filename string, data []byte) (ast.Expr, error) {
176
187
return n , nil
177
188
}
178
189
179
- func (d * decoder ) extract (yn * yaml.Node ) (ast.Expr , error ) {
180
- d .addHeadCommentsToPending (yn )
181
- var expr ast.Expr
182
- var err error
190
+ func (d * decoder ) extractNoAnchor (yn * yaml.Node ) (ast.Expr , error ) {
183
191
switch yn .Kind {
184
192
case yaml .DocumentNode :
185
- expr , err = d .document (yn )
193
+ return d .document (yn )
186
194
case yaml .SequenceNode :
187
- expr , err = d .sequence (yn )
195
+ return d .sequence (yn )
188
196
case yaml .MappingNode :
189
- expr , err = d .mapping (yn )
197
+ return d .mapping (yn )
190
198
case yaml .ScalarNode :
191
- expr , err = d .scalar (yn )
199
+ return d .scalar (yn )
192
200
case yaml .AliasNode :
193
- expr , err = d . alias (yn )
201
+ return d . referenceAlias (yn )
194
202
default :
195
203
return nil , d .posErrorf (yn , "unknown yaml node kind: %d" , yn .Kind )
196
204
}
205
+ }
206
+
207
+ func (d * decoder ) extract (yn * yaml.Node ) (ast.Expr , error ) {
208
+ d .addHeadCommentsToPending (yn )
209
+
210
+ var expr ast.Expr
211
+ var err error
212
+
213
+ if yn .Anchor == "" {
214
+ expr , err = d .extractNoAnchor (yn )
215
+ } else {
216
+ expr , err = d .anchor (yn )
217
+ }
218
+
197
219
if err != nil {
198
220
return nil , err
199
221
}
@@ -324,7 +346,41 @@ func (d *decoder) document(yn *yaml.Node) (ast.Expr, error) {
324
346
if n := len (yn .Content ); n != 1 {
325
347
return nil , d .posErrorf (yn , "yaml document nodes are meant to have one content node but have %d" , n )
326
348
}
327
- return d .extract (yn .Content [0 ])
349
+
350
+ expr , err := d .extract (yn .Content [0 ])
351
+ if err != nil {
352
+ return nil , err
353
+ }
354
+
355
+ return d .addAnchorNodes (expr )
356
+ }
357
+
358
+ // addAnchorNodes prepends anchor nodes at the top of the document.
359
+ func (d * decoder ) addAnchorNodes (expr ast.Expr ) (ast.Expr , error ) {
360
+ elements := []ast.Decl {}
361
+
362
+ for _ , field := range d .anchorFields {
363
+ elements = append (elements , & field )
364
+ }
365
+
366
+ switch x := expr .(type ) {
367
+ case * ast.StructLit :
368
+ x .Elts = append (elements , x .Elts ... )
369
+ break
370
+ case * ast.ListLit :
371
+ if len (elements ) > 0 {
372
+ expr = & ast.StructLit {
373
+ Elts : append (elements , x ),
374
+ }
375
+ }
376
+ break
377
+ default :
378
+ // If the whole YAML document is not a map / seq, then it can't have anchors.
379
+ // maybe assert that `anchorFields` is empty?
380
+ break
381
+ }
382
+
383
+ return expr , nil
328
384
}
329
385
330
386
func (d * decoder ) sequence (yn * yaml.Node ) (ast.Expr , error ) {
@@ -458,7 +514,7 @@ func (d *decoder) label(yn *yaml.Node) (ast.Label, error) {
458
514
if yn .Alias .Kind != yaml .ScalarNode {
459
515
return nil , d .posErrorf (yn , "invalid map key: %v" , yn .Alias .ShortTag ())
460
516
}
461
- expr , err = d .alias (yn )
517
+ expr , err = d .inlineAlias (yn )
462
518
value = yn .Alias .Value
463
519
default :
464
520
return nil , d .posErrorf (yn , "invalid map key: %v" , yn .ShortTag ())
@@ -639,7 +695,10 @@ func (d *decoder) makeNum(yn *yaml.Node, val string, kind token.Token) (expr ast
639
695
return expr
640
696
}
641
697
642
- func (d * decoder ) alias (yn * yaml.Node ) (ast.Expr , error ) {
698
+ // inlineAlias expands an alias node in place, returning the expanded node.
699
+ // Sometimes we have to resort to this, for example when the alias
700
+ // is inside a map key, since CUE does not support structs as map keys.
701
+ func (d * decoder ) inlineAlias (yn * yaml.Node ) (ast.Expr , error ) {
643
702
if d .extractingAliases [yn ] {
644
703
// TODO this could actually be allowed in some circumstances.
645
704
return nil , d .posErrorf (yn , "anchor %q value contains itself" , yn .Value )
@@ -649,11 +708,58 @@ func (d *decoder) alias(yn *yaml.Node) (ast.Expr, error) {
649
708
}
650
709
d .extractingAliases [yn ] = true
651
710
var node ast.Expr
652
- node , err := d .extract (yn .Alias )
711
+ node , err := d .extractNoAnchor (yn .Alias )
653
712
delete (d .extractingAliases , yn )
654
713
return node , err
655
714
}
656
715
716
+ // referenceAlias replaces an alias with a reference to the identifier of its anchor.
717
+ func (d * decoder ) referenceAlias (yn * yaml.Node ) (ast.Expr , error ) {
718
+ anchor , ok := d .anchorNames [yn .Alias ]
719
+ if ! ok {
720
+ return nil , d .posErrorf (yn , "anchor %q not found" , yn .Alias .Anchor )
721
+ }
722
+
723
+ return & ast.Ident {
724
+ NamePos : d .pos (yn ),
725
+ Name : anchor ,
726
+ }, nil
727
+ }
728
+
729
+ func (d * decoder ) anchor (yn * yaml.Node ) (ast.Expr , error ) {
730
+ var anchorIdent string
731
+
732
+ // Pick a non-conflicting anchor name.
733
+ for i := 1 ; ; i ++ {
734
+ if i == 1 {
735
+ anchorIdent = "#" + yn .Anchor
736
+ } else {
737
+ anchorIdent = "#" + yn .Anchor + "_" + strconv .Itoa (i )
738
+ }
739
+ if _ , ok := d .anchorTakenNames [anchorIdent ]; ! ok {
740
+ d .anchorTakenNames [anchorIdent ] = struct {}{}
741
+ break
742
+ }
743
+ }
744
+ d .anchorNames [yn ] = anchorIdent
745
+
746
+ // Process the node itself, but don't put it into the AST just yet,
747
+ // store it for later to be used as an anchor identifier.
748
+ expr , err := d .extractNoAnchor (yn )
749
+ if err != nil {
750
+ return nil , err
751
+ }
752
+ d .anchorFields = append (d .anchorFields , ast.Field {
753
+ Label : & ast.Ident {Name : anchorIdent },
754
+ Value : expr ,
755
+ })
756
+
757
+ return & ast.Ident {
758
+ NamePos : d .pos (yn ),
759
+ Name : anchorIdent ,
760
+ }, nil
761
+ }
762
+
657
763
func labelStr (l ast.Label ) string {
658
764
switch l := l .(type ) {
659
765
case * ast.Ident :
0 commit comments