@@ -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,39 @@ 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
+ case * ast.ListLit :
370
+ if len (elements ) > 0 {
371
+ expr = & ast.StructLit {
372
+ Elts : append (elements , x ),
373
+ }
374
+ }
375
+ default :
376
+ // If the whole YAML document is not a map / seq, then it can't have anchors.
377
+ // maybe assert that `anchorFields` is empty?
378
+ break
379
+ }
380
+
381
+ return expr , nil
328
382
}
329
383
330
384
func (d * decoder ) sequence (yn * yaml.Node ) (ast.Expr , error ) {
@@ -458,7 +512,7 @@ func (d *decoder) label(yn *yaml.Node) (ast.Label, error) {
458
512
if yn .Alias .Kind != yaml .ScalarNode {
459
513
return nil , d .posErrorf (yn , "invalid map key: %v" , yn .Alias .ShortTag ())
460
514
}
461
- expr , err = d .alias (yn )
515
+ expr , err = d .inlineAlias (yn )
462
516
value = yn .Alias .Value
463
517
default :
464
518
return nil , d .posErrorf (yn , "invalid map key: %v" , yn .ShortTag ())
@@ -639,7 +693,10 @@ func (d *decoder) makeNum(yn *yaml.Node, val string, kind token.Token) (expr ast
639
693
return expr
640
694
}
641
695
642
- func (d * decoder ) alias (yn * yaml.Node ) (ast.Expr , error ) {
696
+ // inlineAlias expands an alias node in place, returning the expanded node.
697
+ // Sometimes we have to resort to this, for example when the alias
698
+ // is inside a map key, since CUE does not support structs as map keys.
699
+ func (d * decoder ) inlineAlias (yn * yaml.Node ) (ast.Expr , error ) {
643
700
if d .extractingAliases [yn ] {
644
701
// TODO this could actually be allowed in some circumstances.
645
702
return nil , d .posErrorf (yn , "anchor %q value contains itself" , yn .Value )
@@ -649,11 +706,58 @@ func (d *decoder) alias(yn *yaml.Node) (ast.Expr, error) {
649
706
}
650
707
d .extractingAliases [yn ] = true
651
708
var node ast.Expr
652
- node , err := d .extract (yn .Alias )
709
+ node , err := d .extractNoAnchor (yn .Alias )
653
710
delete (d .extractingAliases , yn )
654
711
return node , err
655
712
}
656
713
714
+ // referenceAlias replaces an alias with a reference to the identifier of its anchor.
715
+ func (d * decoder ) referenceAlias (yn * yaml.Node ) (ast.Expr , error ) {
716
+ anchor , ok := d .anchorNames [yn .Alias ]
717
+ if ! ok {
718
+ return nil , d .posErrorf (yn , "anchor %q not found" , yn .Alias .Anchor )
719
+ }
720
+
721
+ return & ast.Ident {
722
+ NamePos : d .pos (yn ),
723
+ Name : anchor ,
724
+ }, nil
725
+ }
726
+
727
+ func (d * decoder ) anchor (yn * yaml.Node ) (ast.Expr , error ) {
728
+ var anchorIdent string
729
+
730
+ // Pick a non-conflicting anchor name.
731
+ for i := 1 ; ; i ++ {
732
+ if i == 1 {
733
+ anchorIdent = "#" + yn .Anchor
734
+ } else {
735
+ anchorIdent = "#" + yn .Anchor + "_" + strconv .Itoa (i )
736
+ }
737
+ if _ , ok := d .anchorTakenNames [anchorIdent ]; ! ok {
738
+ d .anchorTakenNames [anchorIdent ] = struct {}{}
739
+ break
740
+ }
741
+ }
742
+ d .anchorNames [yn ] = anchorIdent
743
+
744
+ // Process the node itself, but don't put it into the AST just yet,
745
+ // store it for later to be used as an anchor identifier.
746
+ expr , err := d .extractNoAnchor (yn )
747
+ if err != nil {
748
+ return nil , err
749
+ }
750
+ d .anchorFields = append (d .anchorFields , ast.Field {
751
+ Label : & ast.Ident {Name : anchorIdent },
752
+ Value : expr ,
753
+ })
754
+
755
+ return & ast.Ident {
756
+ NamePos : d .pos (yn ),
757
+ Name : anchorIdent ,
758
+ }, nil
759
+ }
760
+
657
761
func labelStr (l ast.Label ) string {
658
762
switch l := l .(type ) {
659
763
case * ast.Ident :
0 commit comments