@@ -64,6 +64,15 @@ type decoder struct {
6464
6565 // forceNewline ensures that the next position will be on a new line.
6666 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 {}
6776}
6877
6978// 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 {
8392 tokFile := token .NewFile (filename , 0 , len (b )+ 1 )
8493 tokFile .SetLinesForContent (b )
8594 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 {}),
89100 }
90101}
91102
@@ -176,24 +187,35 @@ func Unmarshal(filename string, data []byte) (ast.Expr, error) {
176187 return n , nil
177188}
178189
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 ) {
183191 switch yn .Kind {
184192 case yaml .DocumentNode :
185- expr , err = d .document (yn )
193+ return d .document (yn )
186194 case yaml .SequenceNode :
187- expr , err = d .sequence (yn )
195+ return d .sequence (yn )
188196 case yaml .MappingNode :
189- expr , err = d .mapping (yn )
197+ return d .mapping (yn )
190198 case yaml .ScalarNode :
191- expr , err = d .scalar (yn )
199+ return d .scalar (yn )
192200 case yaml .AliasNode :
193- expr , err = d . alias (yn )
201+ return d . referenceAlias (yn )
194202 default :
195203 return nil , d .posErrorf (yn , "unknown yaml node kind: %d" , yn .Kind )
196204 }
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+
197219 if err != nil {
198220 return nil , err
199221 }
@@ -324,7 +346,39 @@ func (d *decoder) document(yn *yaml.Node) (ast.Expr, error) {
324346 if n := len (yn .Content ); n != 1 {
325347 return nil , d .posErrorf (yn , "yaml document nodes are meant to have one content node but have %d" , n )
326348 }
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
328382}
329383
330384func (d * decoder ) sequence (yn * yaml.Node ) (ast.Expr , error ) {
@@ -458,7 +512,7 @@ func (d *decoder) label(yn *yaml.Node) (ast.Label, error) {
458512 if yn .Alias .Kind != yaml .ScalarNode {
459513 return nil , d .posErrorf (yn , "invalid map key: %v" , yn .Alias .ShortTag ())
460514 }
461- expr , err = d .alias (yn )
515+ expr , err = d .inlineAlias (yn )
462516 value = yn .Alias .Value
463517 default :
464518 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
639693 return expr
640694}
641695
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 ) {
643700 if d .extractingAliases [yn ] {
644701 // TODO this could actually be allowed in some circumstances.
645702 return nil , d .posErrorf (yn , "anchor %q value contains itself" , yn .Value )
@@ -649,11 +706,59 @@ func (d *decoder) alias(yn *yaml.Node) (ast.Expr, error) {
649706 }
650707 d .extractingAliases [yn ] = true
651708 var node ast.Expr
652- node , err := d .extract (yn .Alias )
709+ node , err := d .extractNoAnchor (yn .Alias )
653710 delete (d .extractingAliases , yn )
654711 return node , err
655712}
656713
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+ pos := d .pos (yn )
747+ expr , err := d .extractNoAnchor (yn )
748+ if err != nil {
749+ return nil , err
750+ }
751+ d .anchorFields = append (d .anchorFields , ast.Field {
752+ Label : & ast.Ident {Name : anchorIdent },
753+ Value : expr ,
754+ })
755+
756+ return & ast.Ident {
757+ NamePos : pos ,
758+ Name : anchorIdent ,
759+ }, nil
760+ }
761+
657762func labelStr (l ast.Label ) string {
658763 switch l := l .(type ) {
659764 case * ast.Ident :
0 commit comments