From 7d56fe29f31ec88430452ac74bfe82e088099ec6 Mon Sep 17 00:00:00 2001 From: Masaaki Goshima Date: Wed, 18 Dec 2024 10:32:00 +0900 Subject: [PATCH] support merge tag (#597) --- ast/ast.go | 87 +++++++++++++++++++++++++++++++++++++++++++++++++ decode.go | 18 +++++----- decode_test.go | 7 ++++ parser/token.go | 32 ++++++++++++++++-- token/token.go | 12 +++++++ 5 files changed, 144 insertions(+), 12 deletions(-) diff --git a/ast/ast.go b/ast/ast.go index 53ad0daa..b6c9663d 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -196,6 +196,7 @@ type Node interface { // MapKeyNode type for map key node type MapKeyNode interface { Node + IsMergeKey() bool // String node to text without comment stringWithoutComment() string } @@ -633,6 +634,11 @@ func (n *NullNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *NullNode) IsMergeKey() bool { + return false +} + // IntegerNode type of integer node type IntegerNode struct { *BaseNode @@ -680,6 +686,11 @@ func (n *IntegerNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *IntegerNode) IsMergeKey() bool { + return false +} + // FloatNode type of float node type FloatNode struct { *BaseNode @@ -728,6 +739,11 @@ func (n *FloatNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *FloatNode) IsMergeKey() bool { + return false +} + // StringNode type of string node type StringNode struct { *BaseNode @@ -758,6 +774,11 @@ func (n *StringNode) GetValue() interface{} { return n.Value } +// IsMergeKey returns whether it is a MergeKey node. +func (n *StringNode) IsMergeKey() bool { + return false +} + // escapeSingleQuote escapes s to a single quoted scalar. // https://yaml.org/spec/1.2.2/#732-single-quoted-style func escapeSingleQuote(s string) string { @@ -895,6 +916,11 @@ func (n *LiteralNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *LiteralNode) IsMergeKey() bool { + return false +} + // MergeKeyNode type of merge key node type MergeKeyNode struct { *BaseNode @@ -938,6 +964,11 @@ func (n *MergeKeyNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *MergeKeyNode) IsMergeKey() bool { + return true +} + // BoolNode type of boolean node type BoolNode struct { *BaseNode @@ -985,6 +1016,11 @@ func (n *BoolNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *BoolNode) IsMergeKey() bool { + return false +} + // InfinityNode type of infinity node type InfinityNode struct { *BaseNode @@ -1032,6 +1068,11 @@ func (n *InfinityNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *InfinityNode) IsMergeKey() bool { + return false +} + // NanNode type of nan node type NanNode struct { *BaseNode @@ -1078,6 +1119,11 @@ func (n *NanNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *NanNode) IsMergeKey() bool { + return false +} + // MapNode interface of MappingValueNode / MappingNode type MapNode interface { MapRange() *MapNodeIter @@ -1281,6 +1327,18 @@ func (n *MappingKeyNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *MappingKeyNode) IsMergeKey() bool { + if n.Value == nil { + return false + } + key, ok := n.Value.(MapKeyNode) + if !ok { + return false + } + return key.IsMergeKey() +} + // MappingValueNode type of mapping value type MappingValueNode struct { *BaseNode @@ -1665,6 +1723,18 @@ func (n *AnchorNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *AnchorNode) IsMergeKey() bool { + if n.Value == nil { + return false + } + key, ok := n.Value.(MapKeyNode) + if !ok { + return false + } + return key.IsMergeKey() +} + // AliasNode type of alias node type AliasNode struct { *BaseNode @@ -1723,6 +1793,11 @@ func (n *AliasNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *AliasNode) IsMergeKey() bool { + return false +} + // DirectiveNode type of directive node type DirectiveNode struct { *BaseNode @@ -1822,6 +1897,18 @@ func (n *TagNode) MarshalYAML() ([]byte, error) { return []byte(n.String()), nil } +// IsMergeKey returns whether it is a MergeKey node. +func (n *TagNode) IsMergeKey() bool { + if n.Value == nil { + return false + } + key, ok := n.Value.(MapKeyNode) + if !ok { + return false + } + return key.IsMergeKey() +} + // CommentNode type of comment node type CommentNode struct { *BaseNode diff --git a/decode.go b/decode.go index 024e9bad..82915cbc 100644 --- a/decode.go +++ b/decode.go @@ -147,7 +147,7 @@ func (d *Decoder) setToMapValue(node ast.Node, m map[string]interface{}) error { d.setPathToCommentMap(node) switch n := node.(type) { case *ast.MappingValueNode: - if n.Key.Type() == ast.MergeKeyType { + if n.Key.IsMergeKey() { if err := d.setToMapValue(d.mergeValueNode(n.Value), m); err != nil { return err } @@ -185,7 +185,7 @@ func (d *Decoder) setToOrderedMapValue(node ast.Node, m *MapSlice) error { d.setPathToCommentMap(node) switch n := node.(type) { case *ast.MappingValueNode: - if n.Key.Type() == ast.MergeKeyType { + if n.Key.IsMergeKey() { if err := d.setToOrderedMapValue(d.mergeValueNode(n.Value), m); err != nil { return err } @@ -464,7 +464,7 @@ func (d *Decoder) nodeToValue(node ast.Node) (any, error) { case *ast.MappingKeyNode: return d.nodeToValue(n.Value) case *ast.MappingValueNode: - if n.Key.Type() == ast.MergeKeyType { + if n.Key.IsMergeKey() { value := d.mergeValueNode(n.Value) if d.useOrderedMap { m := MapSlice{} @@ -555,7 +555,7 @@ func (d *Decoder) resolveAlias(node ast.Node) (ast.Node, error) { } n.Value = value case *ast.MappingValueNode: - if n.Key.Type() == ast.MergeKeyType && n.Value.Type() == ast.AliasType { + if n.Key.IsMergeKey() && n.Value.Type() == ast.AliasType { value, err := d.resolveAlias(n.Value) if err != nil { return nil, err @@ -1186,7 +1186,7 @@ func (d *Decoder) keyToNodeMap(node ast.Node, ignoreMergeKey bool, getKeyOrValue mapIter := mapNode.MapRange() for mapIter.Next() { keyNode := mapIter.Key() - if keyNode.Type() == ast.MergeKeyType { + if keyNode.IsMergeKey() { if ignoreMergeKey { continue } @@ -1349,7 +1349,7 @@ func (d *Decoder) getMergeAliasName(src ast.Node) string { for mapIter.Next() { key := mapIter.Key() value := mapIter.Value() - if key.Type() == ast.MergeKeyType && value.Type() == ast.AliasType { + if key.IsMergeKey() && value.Type() == ast.AliasType { return value.(*ast.AliasNode).Value.GetToken().Value } } @@ -1639,7 +1639,7 @@ func (d *Decoder) decodeMapItem(ctx context.Context, dst *MapItem, src ast.Node) } key := mapIter.Key() value := mapIter.Value() - if key.Type() == ast.MergeKeyType { + if key.IsMergeKey() { if err := d.decodeMapItem(ctx, dst, value); err != nil { return err } @@ -1691,7 +1691,7 @@ func (d *Decoder) decodeMapSlice(ctx context.Context, dst *MapSlice, src ast.Nod for mapIter.Next() { key := mapIter.Key() value := mapIter.Value() - if key.Type() == ast.MergeKeyType { + if key.IsMergeKey() { var m MapSlice if err := d.decodeMapSlice(ctx, &m, value); err != nil { return err @@ -1745,7 +1745,7 @@ func (d *Decoder) decodeMap(ctx context.Context, dst reflect.Value, src ast.Node for mapIter.Next() { key := mapIter.Key() value := mapIter.Value() - if key.Type() == ast.MergeKeyType { + if key.IsMergeKey() { if err := d.decodeMap(ctx, dst, value); err != nil { return err } diff --git a/decode_test.go b/decode_test.go index 643f9aa4..4f0fc11e 100644 --- a/decode_test.go +++ b/decode_test.go @@ -590,6 +590,13 @@ func TestDecoder(t *testing.T) { "v: !!bool False", map[string]bool{"v": false}, }, + { + ` +!!merge <<: { a: 1, b: 2 } +c: 3 +`, + map[string]any{"a": 1, "b": 2, "c": 3}, + }, // Flow sequence { diff --git a/parser/token.go b/parser/token.go index 3d1c06f1..42f0a9b1 100644 --- a/parser/token.go +++ b/parser/token.go @@ -213,7 +213,10 @@ func createGroupedTokens(tokens token.Tokens) ([]*Token, error) { if err != nil { return nil, err } - tks = createScalarTagTokenGroups(tks) + tks, err = createScalarTagTokenGroups(tks) + if err != nil { + return nil, err + } tks, err = createAnchorWithScalarTagTokenGroups(tks) if err != nil { return nil, err @@ -348,7 +351,7 @@ func createAnchorAndAliasTokenGroups(tokens []*Token) ([]*Token, error) { return ret, nil } -func createScalarTagTokenGroups(tokens []*Token) []*Token { +func createScalarTagTokenGroups(tokens []*Token) ([]*Token, error) { ret := make([]*Token, 0, len(tokens)) for i := 0; i < len(tokens); i++ { tk := tokens[i] @@ -384,6 +387,29 @@ func createScalarTagTokenGroups(tokens []*Token) []*Token { } else { ret = append(ret, tk) } + case token.MergeTag: + if len(tokens) <= i+1 { + ret = append(ret, tk) + continue + } + if tk.Line() != tokens[i+1].Line() { + ret = append(ret, tk) + continue + } + if tokens[i+1].GroupType() == TokenGroupAnchorName { + ret = append(ret, tk) + continue + } + if tokens[i+1].Type() != token.MergeKeyType { + return nil, errors.ErrSyntax("could not find merge key", tokens[i+1].RawToken()) + } + ret = append(ret, &Token{ + Group: &TokenGroup{ + Type: TokenGroupScalarTag, + Tokens: []*Token{tk, tokens[i+1]}, + }, + }) + i++ default: ret = append(ret, tk) } @@ -409,7 +435,7 @@ func createScalarTagTokenGroups(tokens []*Token) []*Token { i++ } } - return ret + return ret, nil } func createAnchorWithScalarTagTokenGroups(tokens []*Token) ([]*Token, error) { diff --git a/token/token.go b/token/token.go index 21464cf6..54ab1799 100644 --- a/token/token.go +++ b/token/token.go @@ -405,6 +405,8 @@ const ( TimestampTag ReservedTagKeyword = "!!timestamp" // BooleanTag `!!bool` tag BooleanTag ReservedTagKeyword = "!!bool" + // MergeTag `!!merge` tag + MergeTag ReservedTagKeyword = "!!merge" ) var ( @@ -520,6 +522,16 @@ var ( Position: pos, } }, + MergeTag: func(value, org string, pos *Position) *Token { + return &Token{ + Type: TagType, + CharacterType: CharacterTypeIndicator, + Indicator: NodePropertyIndicator, + Value: value, + Origin: org, + Position: pos, + } + }, } )