Repository: goccy/go-yaml
Pull request #233 (2021-07-16 12:54:26)

Support encoding of comment node
Supports comment encoding.
This allows you to parse the following document with comments and undo it !

```yaml
# foo comment
# foo comment2
foo: # map key comment
  # bar above comment
  # bar above comment2
  bar: 10 # comment for bar
  # baz above comment
  # baz above comment2
  baz: bbbb # comment for baz
  piyo: # sequence key comment
  # sequence1 above comment 1
  # sequence1 above comment 2
  - sequence1 # sequence1
  # sequence2 above comment 1
  # sequence2 above comment 2
  - sequence2 # sequence2
  # sequence3 above comment 1
  # sequence3 above comment 2
  - false # sequence3
# foo2 comment
# foo2 comment2
foo2: &anchor text # anchor comment
# foo3 comment
# foo3 comment2
foo3: *anchor # alias comment
```

```go
f, err := parser.ParseBytes([]byte(yml), parser.ParseComments)
if err != nil {
  panic(err)
}
fmt.Println(f.String()) // You can get the same as the original document
```



Patch:
diff --git a/ast/ast.go b/ast/ast.go
index eee3a87..b035b36 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -61,6 +61,8 @@ const (
 	TagType
 	// CommentType type identifier for comment node
 	CommentType
+	// CommentGroupType type identifier for comment group node
+	CommentGroupType
 )
 
 // String node type identifier to text
@@ -106,6 +108,8 @@ func (t NodeType) String() string {
 		return "Tag"
 	case CommentType:
 		return "Comment"
+	case CommentGroupType:
+		return "CommentGroup"
 	}
 	return ""
 }
@@ -154,6 +158,8 @@ func (t NodeType) YAMLName() string {
 		return "tag"
 	case CommentType:
 		return "comment"
+	case CommentGroupType:
+		return "comment"
 	}
 	return ""
 }
@@ -170,9 +176,9 @@ type Node interface {
 	// AddColumn add column number to child nodes recursively
 	AddColumn(int)
 	// SetComment set comment token to node
-	SetComment(*token.Token) error
+	SetComment(*CommentGroupNode) error
 	// Comment returns comment token instance
-	GetComment() *token.Token
+	GetComment() *CommentGroupNode
 	// MarshalYAML
 	MarshalYAML() ([]byte, error)
 	// already read length
@@ -181,6 +187,8 @@ type Node interface {
 	addReadLen(int)
 	// clean read length
 	clearLen()
+	// String node to text without comment
+	stringWithoutComment() string
 }
 
 // ScalarNode type for scalar node
@@ -190,10 +198,14 @@ type ScalarNode interface {
 }
 
 type BaseNode struct {
-	Comment *token.Token
+	Comment *CommentGroupNode
 	read    int
 }
 
+func addCommentString(base string, node *CommentGroupNode) string {
+	return fmt.Sprintf("%s %s", base, node.String())
+}
+
 func (n *BaseNode) readLen() int {
 	return n.read
 }
@@ -207,16 +219,13 @@ func (n *BaseNode) addReadLen(len int) {
 }
 
 // GetComment returns comment token instance
-func (n *BaseNode) GetComment() *token.Token {
+func (n *BaseNode) GetComment() *CommentGroupNode {
 	return n.Comment
 }
 
 // SetComment set comment token
-func (n *BaseNode) SetComment(tk *token.Token) error {
-	if tk.Type != token.CommentType {
-		return ErrInvalidTokenType
-	}
-	n.Comment = tk
+func (n *BaseNode) SetComment(node *CommentGroupNode) error {
+	n.Comment = node
 	return nil
 }
 
@@ -400,7 +409,19 @@ func String(tk *token.Token) *StringNode {
 // Comment create node for comment
 func Comment(tk *token.Token) *CommentNode {
 	return &CommentNode{
-		BaseNode: &BaseNode{Comment: tk},
+		BaseNode: &BaseNode{},
+		Token:    tk,
+	}
+}
+
+func CommentGroup(comments []*token.Token) *CommentGroupNode {
+	nodes := []*CommentNode{}
+	for _, comment := range comments {
+		nodes = append(nodes, Comment(comment))
+	}
+	return &CommentGroupNode{
+		BaseNode: &BaseNode{},
+		Comments: nodes,
 	}
 }
 
@@ -522,6 +543,10 @@ func (f *File) String() string {
 	return strings.Join(docs, "\n")
 }
 
+func (f *File) stringWithoutComment() string {
+	return f.String()
+}
+
 // DocumentNode type of Document
 type DocumentNode struct {
 	*BaseNode
@@ -563,6 +588,10 @@ func (d *DocumentNode) String() string {
 	return strings.Join(doc, "\n")
 }
 
+func (d *DocumentNode) stringWithoutComment() string {
+	return d.String()
+}
+
 // MarshalYAML encodes to a YAML text
 func (d *DocumentNode) MarshalYAML() ([]byte, error) {
 	return []byte(d.String()), nil
@@ -575,8 +604,7 @@ func removeUnderScoreFromNumber(num string) string {
 // NullNode type of null node
 type NullNode struct {
 	*BaseNode
-	Comment *token.Token // position of Comment ( `#comment` )
-	Token   *token.Token
+	Token *token.Token
 }
 
 // Read implements (io.Reader).Read
@@ -597,15 +625,6 @@ func (n *NullNode) AddColumn(col int) {
 	n.Token.AddColumn(col)
 }
 
-// SetComment set comment token
-func (n *NullNode) SetComment(tk *token.Token) error {
-	if tk.Type != token.CommentType {
-		return ErrInvalidTokenType
-	}
-	n.Comment = tk
-	return nil
-}
-
 // GetValue returns nil value
 func (n *NullNode) GetValue() interface{} {
 	return nil
@@ -613,6 +632,13 @@ func (n *NullNode) GetValue() interface{} {
 
 // String returns `null` text
 func (n *NullNode) String() string {
+	if n.Comment != nil {
+		return fmt.Sprintf("null %s", n.Comment.String())
+	}
+	return n.stringWithoutComment()
+}
+
+func (n *NullNode) stringWithoutComment() string {
 	return "null"
 }
 
@@ -653,6 +679,13 @@ func (n *IntegerNode) GetValue() interface{} {
 
 // String int64 to text
 func (n *IntegerNode) String() string {
+	if n.Comment != nil {
+		return addCommentString(n.Token.Value, n.Comment)
+	}
+	return n.stringWithoutComment()
+}
+
+func (n *IntegerNode) stringWithoutComment() string {
 	return n.Token.Value
 }
 
@@ -694,6 +727,13 @@ func (n *FloatNode) GetValue() interface{} {
 
 // String float64 to text
 func (n *FloatNode) String() string {
+	if n.Comment != nil {
+		return addCommentString(n.Token.Value, n.Comment)
+	}
+	return n.stringWithoutComment()
+}
+
+func (n *FloatNode) stringWithoutComment() string {
 	return n.Token.Value
 }
 
@@ -736,9 +776,48 @@ func (n *StringNode) GetValue() interface{} {
 func (n *StringNode) String() string {
 	switch n.Token.Type {
 	case token.SingleQuoteType:
+		quoted := fmt.Sprintf(`'%s'`, n.Value)
+		if n.Comment != nil {
+			return addCommentString(quoted, n.Comment)
+		}
+		return quoted
+	case token.DoubleQuoteType:
+		quoted := strconv.Quote(n.Value)
+		if n.Comment != nil {
+			return addCommentString(quoted, n.Comment)
+		}
+		return quoted
+	}
+
+	lbc := token.DetectLineBreakCharacter(n.Value)
+	if strings.Contains(n.Value, lbc) {
+		// This block assumes that the line breaks in this inside scalar content and the Outside scalar content are the same.
+		// It works mostly, but inconsistencies occur if line break characters are mixed.
+		header := token.LiteralBlockHeader(n.Value)
+		space := strings.Repeat(" ", n.Token.Position.Column-1)
+		values := []string{}
+		for _, v := range strings.Split(n.Value, lbc) {
+			values = append(values, fmt.Sprintf("%s  %s", space, v))
+		}
+		block := strings.TrimSuffix(strings.TrimSuffix(strings.Join(values, lbc), fmt.Sprintf("%s  %s", lbc, space)), fmt.Sprintf("  %s", space))
+		return fmt.Sprintf("%s%s%s", header, lbc, block)
+	} else if len(n.Value) > 0 && (n.Value[0] == '{' || n.Value[0] == '[') {
 		return fmt.Sprintf(`'%s'`, n.Value)
+	}
+	if n.Comment != nil {
+		return addCommentString(n.Value, n.Comment)
+	}
+	return n.Value
+}
+
+func (n *StringNode) stringWithoutComment() string {
+	switch n.Token.Type {
+	case token.SingleQuoteType:
+		quoted := fmt.Sprintf(`'%s'`, n.Value)
+		return quoted
 	case token.DoubleQuoteType:
-		return strconv.Quote(n.Value)
+		quoted := strconv.Quote(n.Value)
+		return quoted
 	}
 
 	lbc := token.DetectLineBreakCharacter(n.Value)
@@ -803,6 +882,10 @@ func (n *LiteralNode) String() string {
 	return fmt.Sprintf("%s\n%s", n.Start.Value, strings.TrimRight(strings.TrimRight(origin, " "), "\n"))
 }
 
+func (n *LiteralNode) stringWithoutComment() string {
+	return n.String()
+}
+
 // MarshalYAML encodes to a YAML text
 func (n *LiteralNode) MarshalYAML() ([]byte, error) {
 	return []byte(n.String()), nil
@@ -837,6 +920,10 @@ func (n *MergeKeyNode) String() string {
 	return n.Token.Value
 }
 
+func (n *MergeKeyNode) stringWithoutComment() string {
+	return n.Token.Value
+}
+
 // AddColumn add column number to child nodes recursively
 func (n *MergeKeyNode) AddColumn(col int) {
 	n.Token.AddColumn(col)
@@ -879,6 +966,13 @@ func (n *BoolNode) GetValue() interface{} {
 
 // String boolean to text
 func (n *BoolNode) String() string {
+	if n.Comment != nil {
+		return addCommentString(n.Token.Value, n.Comment)
+	}
+	return n.stringWithoutComment()
+}
+
+func (n *BoolNode) stringWithoutComment() string {
 	return n.Token.Value
 }
 
@@ -919,6 +1013,13 @@ func (n *InfinityNode) GetValue() interface{} {
 
 // String infinity to text
 func (n *InfinityNode) String() string {
+	if n.Comment != nil {
+		return addCommentString(n.Token.Value, n.Comment)
+	}
+	return n.stringWithoutComment()
+}
+
+func (n *InfinityNode) stringWithoutComment() string {
 	return n.Token.Value
 }
 
@@ -958,6 +1059,13 @@ func (n *NanNode) GetValue() interface{} {
 
 // String returns .nan
 func (n *NanNode) String() string {
+	if n.Comment != nil {
+		return addCommentString(n.Token.Value, n.Comment)
+	}
+	return n.stringWithoutComment()
+}
+
+func (n *NanNode) stringWithoutComment() string {
 	return n.Token.Value
 }
 
@@ -1064,34 +1172,64 @@ func (n *MappingNode) AddColumn(col int) {
 	}
 }
 
-func (n *MappingNode) flowStyleString() string {
-	if len(n.Values) == 0 {
-		return "{}"
-	}
+func (n *MappingNode) flowStyleString(commentMode bool) string {
 	values := []string{}
 	for _, value := range n.Values {
 		values = append(values, strings.TrimLeft(value.String(), " "))
 	}
-	return fmt.Sprintf("{%s}", strings.Join(values, ", "))
+	mapText := fmt.Sprintf("{%s}", strings.Join(values, ", "))
+	if commentMode && n.Comment != nil {
+		return addCommentString(mapText, n.Comment)
+	}
+	return mapText
 }
 
-func (n *MappingNode) blockStyleString() string {
-	if len(n.Values) == 0 {
-		return "{}"
-	}
+func (n *MappingNode) blockStyleString(commentMode bool) string {
 	values := []string{}
 	for _, value := range n.Values {
 		values = append(values, value.String())
 	}
-	return strings.Join(values, "\n")
+	mapText := strings.Join(values, "\n")
+	if commentMode && n.Comment != nil {
+		value := values[0]
+		var spaceNum int
+		for i := 0; i < len(value); i++ {
+			if value[i] != ' ' {
+				break
+			}
+			spaceNum++
+		}
+		comment := fmt.Sprintf(
+			"%s",
+			n.Comment.StringWithSpace(spaceNum),
+		)
+		return fmt.Sprintf("%s\n%s", comment, mapText)
+	}
+	return mapText
 }
 
 // String mapping values to text
 func (n *MappingNode) String() string {
+	if len(n.Values) == 0 {
+		if n.Comment != nil {
+			return addCommentString("{}", n.Comment)
+		}
+		return "{}"
+	}
+
+	commentMode := true
 	if n.IsFlowStyle || len(n.Values) == 0 {
-		return n.flowStyleString()
+		return n.flowStyleString(commentMode)
 	}
-	return n.blockStyleString()
+	return n.blockStyleString(commentMode)
+}
+
+func (n *MappingNode) stringWithoutComment() string {
+	commentMode := false
+	if n.IsFlowStyle || len(n.Values) == 0 {
+		return n.flowStyleString(commentMode)
+	}
+	return n.blockStyleString(commentMode)
 }
 
 // MapRange implements MapNode protocol
@@ -1140,6 +1278,10 @@ func (n *MappingKeyNode) String() string {
 	return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String())
 }
 
+func (n *MappingKeyNode) stringWithoutComment() string {
+	return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String())
+}
+
 // MarshalYAML encodes to a YAML text
 func (n *MappingKeyNode) MarshalYAML() ([]byte, error) {
 	return []byte(n.String()), nil
@@ -1199,6 +1341,56 @@ func (n *MappingValueNode) SetIsFlowStyle(isFlow bool) {
 
 // String mapping value to text
 func (n *MappingValueNode) String() string {
+	if n.Comment != nil {
+		return fmt.Sprintf(
+			"%s\n%s",
+			n.Comment.StringWithSpace(n.Key.GetToken().Position.Column-1),
+			n.toString(),
+		)
+	}
+	return n.toString()
+}
+
+func (n *MappingValueNode) toString() string {
+	space := strings.Repeat(" ", n.Key.GetToken().Position.Column-1)
+	keyIndentLevel := n.Key.GetToken().Position.IndentLevel
+	valueIndentLevel := n.Value.GetToken().Position.IndentLevel
+	keyComment := n.Key.GetComment()
+	if _, ok := n.Value.(ScalarNode); ok {
+		return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
+	} else if keyIndentLevel < valueIndentLevel {
+		if keyComment != nil {
+			return fmt.Sprintf(
+				"%s%s: %s\n%s",
+				space,
+				n.Key.stringWithoutComment(),
+				keyComment.String(),
+				n.Value.String(),
+			)
+		}
+		return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String())
+	} else if m, ok := n.Value.(*MappingNode); ok && (m.IsFlowStyle || len(m.Values) == 0) {
+		return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
+	} else if s, ok := n.Value.(*SequenceNode); ok && (s.IsFlowStyle || len(s.Values) == 0) {
+		return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
+	} else if _, ok := n.Value.(*AnchorNode); ok {
+		return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
+	} else if _, ok := n.Value.(*AliasNode); ok {
+		return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
+	}
+	if keyComment != nil {
+		return fmt.Sprintf(
+			"%s%s: %s\n%s",
+			space,
+			n.Key.stringWithoutComment(),
+			keyComment.String(),
+			n.Value.String(),
+		)
+	}
+	return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String())
+}
+
+func (n *MappingValueNode) stringWithoutComment() string {
 	space := strings.Repeat(" ", n.Key.GetToken().Position.Column-1)
 	keyIndentLevel := n.Key.GetToken().Position.IndentLevel
 	valueIndentLevel := n.Value.GetToken().Position.IndentLevel
@@ -1263,10 +1455,11 @@ func (m *ArrayNodeIter) Len() int {
 // SequenceNode type of sequence node
 type SequenceNode struct {
 	*BaseNode
-	Start       *token.Token
-	End         *token.Token
-	IsFlowStyle bool
-	Values      []Node
+	Start         *token.Token
+	End           *token.Token
+	IsFlowStyle   bool
+	Values        []Node
+	ValueComments []*CommentGroupNode
 }
 
 // Replace replace value node.
@@ -1340,7 +1533,11 @@ func (n *SequenceNode) flowStyleString() string {
 func (n *SequenceNode) blockStyleString() string {
 	space := strings.Repeat(" ", n.Start.Position.Column-1)
 	values := []string{}
-	for _, value := range n.Values {
+	if n.Comment != nil {
+		values = append(values, n.Comment.StringWithSpace(n.Start.Position.Column-1))
+	}
+
+	for idx, value := range n.Values {
 		valueStr := value.String()
 		splittedValues := strings.Split(valueStr, "\n")
 		trimmedFirstValue := strings.TrimLeft(splittedValues[0], " ")
@@ -1356,6 +1553,9 @@ func (n *SequenceNode) blockStyleString() string {
 			newValues = append(newValues, fmt.Sprintf("%s  %s", space, trimmed))
 		}
 		newValue := strings.Join(newValues, "\n")
+		if len(n.ValueComments) == len(n.Values) && n.ValueComments[idx] != nil {
+			values = append(values, n.ValueComments[idx].StringWithSpace(n.Start.Position.Column-1))
+		}
 		values = append(values, fmt.Sprintf("%s- %s", space, newValue))
 	}
 	return strings.Join(values, "\n")
@@ -1369,6 +1569,13 @@ func (n *SequenceNode) String() string {
 	return n.blockStyleString()
 }
 
+func (n *SequenceNode) stringWithoutComment() string {
+	if n.IsFlowStyle || len(n.Values) == 0 {
+		return n.flowStyleString()
+	}
+	return n.blockStyleString()
+}
+
 // ArrayRange implements ArrayNode protocol
 func (n *SequenceNode) ArrayRange() *ArrayNodeIter {
 	return &ArrayNodeIter{
@@ -1439,6 +1646,10 @@ func (n *AnchorNode) String() string {
 	return fmt.Sprintf("&%s %s", n.Name.String(), value)
 }
 
+func (n *AnchorNode) stringWithoutComment() string {
+	return n.String()
+}
+
 // MarshalYAML encodes to a YAML text
 func (n *AnchorNode) MarshalYAML() ([]byte, error) {
 	return []byte(n.String()), nil
@@ -1489,6 +1700,10 @@ func (n *AliasNode) String() string {
 	return fmt.Sprintf("*%s", n.Value.String())
 }
 
+func (n *AliasNode) stringWithoutComment() string {
+	return fmt.Sprintf("*%s", n.Value.String())
+}
+
 // MarshalYAML encodes to a YAML text
 func (n *AliasNode) MarshalYAML() ([]byte, error) {
 	return []byte(n.String()), nil
@@ -1526,6 +1741,10 @@ func (n *DirectiveNode) String() string {
 	return fmt.Sprintf("%s%s", n.Start.Value, n.Value.String())
 }
 
+func (n *DirectiveNode) stringWithoutComment() string {
+	return fmt.Sprintf("%s%s", n.Start.Value, n.Value.String())
+}
+
 // MarshalYAML encodes to a YAML text
 func (n *DirectiveNode) MarshalYAML() ([]byte, error) {
 	return []byte(n.String()), nil
@@ -1564,6 +1783,10 @@ func (n *TagNode) String() string {
 	return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String())
 }
 
+func (n *TagNode) stringWithoutComment() string {
+	return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String())
+}
+
 // MarshalYAML encodes to a YAML text
 func (n *TagNode) MarshalYAML() ([]byte, error) {
 	return []byte(n.String()), nil
@@ -1572,6 +1795,7 @@ func (n *TagNode) MarshalYAML() ([]byte, error) {
 // CommentNode type of comment node
 type CommentNode struct {
 	*BaseNode
+	Token *token.Token
 }
 
 // Read implements (io.Reader).Read
@@ -1583,16 +1807,23 @@ func (n *CommentNode) Read(p []byte) (int, error) {
 func (n *CommentNode) Type() NodeType { return CommentType }
 
 // GetToken returns token instance
-func (n *CommentNode) GetToken() *token.Token { return n.Comment }
+func (n *CommentNode) GetToken() *token.Token { return n.Token }
 
 // AddColumn add column number to child nodes recursively
 func (n *CommentNode) AddColumn(col int) {
-	n.Comment.AddColumn(col)
+	if n.Token == nil {
+		return
+	}
+	n.Token.AddColumn(col)
 }
 
 // String comment to text
 func (n *CommentNode) String() string {
-	return n.Comment.Value
+	return fmt.Sprintf("#%s", n.Token.Value)
+}
+
+func (n *CommentNode) stringWithoutComment() string {
+	return ""
 }
 
 // MarshalYAML encodes to a YAML text
@@ -1600,6 +1831,63 @@ func (n *CommentNode) MarshalYAML() ([]byte, error) {
 	return []byte(n.String()), nil
 }
 
+// CommentGroupNode type of comment node
+type CommentGroupNode struct {
+	*BaseNode
+	Comments []*CommentNode
+}
+
+// Read implements (io.Reader).Read
+func (n *CommentGroupNode) Read(p []byte) (int, error) {
+	return readNode(p, n)
+}
+
+// Type returns TagType
+func (n *CommentGroupNode) Type() NodeType { return CommentType }
+
+// GetToken returns token instance
+func (n *CommentGroupNode) GetToken() *token.Token {
+	if len(n.Comments) > 0 {
+		return n.Comments[0].Token
+	}
+	return nil
+}
+
+// AddColumn add column number to child nodes recursively
+func (n *CommentGroupNode) AddColumn(col int) {
+	for _, comment := range n.Comments {
+		comment.AddColumn(col)
+	}
+}
+
+// String comment to text
+func (n *CommentGroupNode) String() string {
+	values := []string{}
+	for _, comment := range n.Comments {
+		values = append(values, comment.String())
+	}
+	return strings.Join(values, "\n")
+}
+
+func (n *CommentGroupNode) StringWithSpace(col int) string {
+	space := strings.Repeat(" ", col)
+	values := []string{}
+	for _, comment := range n.Comments {
+		values = append(values, space+comment.String())
+	}
+	return strings.Join(values, "\n")
+
+}
+
+func (n *CommentGroupNode) stringWithoutComment() string {
+	return ""
+}
+
+// MarshalYAML encodes to a YAML text
+func (n *CommentGroupNode) MarshalYAML() ([]byte, error) {
+	return []byte(n.String()), nil
+}
+
 // Visitor has Visit method that is invokded for each node encountered by Walk.
 // If the result visitor w is not nil, Walk visits each of the children of node with the visitor w,
 // followed by a call of w.Visit(nil).
diff --git a/parser/parser.go b/parser/parser.go
index 27a6a6d..1b646d0 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -260,10 +260,25 @@ func (p *parser) parseSequenceEntry(ctx *context) (ast.Node, error) {
 	curColumn := tk.Position.Column
 	for tk.Type == token.SequenceEntryType {
 		ctx.progress(1) // skip sequence token
+		tk = ctx.currentToken()
+		var comment *ast.CommentGroupNode
+		if tk.Type == token.CommentType {
+			comment = p.parseCommentOnly(ctx)
+			tk = ctx.currentToken()
+			if tk.Type != token.SequenceEntryType {
+				break
+			}
+			ctx.progress(1) // skip sequence token
+		}
 		value, err := p.parseToken(ctx, ctx.currentToken())
 		if err != nil {
 			return nil, errors.Wrapf(err, "failed to parse sequence")
 		}
+		if comment != nil {
+			sequenceNode.ValueComments = append(sequenceNode.ValueComments, comment)
+		} else {
+			sequenceNode.ValueComments = append(sequenceNode.ValueComments, nil)
+		}
 		sequenceNode.Values = append(sequenceNode.Values, value)
 		tk = ctx.nextNotCommentToken()
 		if tk == nil {
@@ -435,7 +450,7 @@ func (p *parser) setSameLineCommentIfExists(ctx *context, node ast.Node) error {
 	if !p.isSameLineComment(tk, node) {
 		return nil
 	}
-	if err := node.SetComment(tk); err != nil {
+	if err := node.SetComment(ast.CommentGroup([]*token.Token{tk})); err != nil {
 		return errors.Wrapf(err, "failed to set comment token to ast.Node")
 	}
 	return nil
@@ -456,7 +471,7 @@ func (p *parser) parseDocument(ctx *context) (*ast.DocumentNode, error) {
 	return node, nil
 }
 
-func (p *parser) parseComment(ctx *context) (ast.Node, error) {
+func (p *parser) parseCommentOnly(ctx *context) *ast.CommentGroupNode {
 	commentTokens := []*token.Token{}
 	for {
 		tk := ctx.currentToken()
@@ -469,24 +484,19 @@ func (p *parser) parseComment(ctx *context) (ast.Node, error) {
 		commentTokens = append(commentTokens, tk)
 		ctx.progressIgnoreComment(1) // skip comment token
 	}
-	// TODO: support token group. currently merge tokens to one token
-	firstToken := commentTokens[0]
-	values := []string{}
-	origins := []string{}
-	for _, tk := range commentTokens {
-		values = append(values, tk.Value)
-		origins = append(origins, tk.Origin)
-	}
-	firstToken.Value = strings.Join(values, "")
-	firstToken.Value = strings.Join(origins, "")
+	return ast.CommentGroup(commentTokens)
+}
+
+func (p *parser) parseComment(ctx *context) (ast.Node, error) {
+	group := p.parseCommentOnly(ctx)
 	node, err := p.parseToken(ctx, ctx.currentToken())
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to parse node after comment")
 	}
 	if node == nil {
-		return ast.Comment(firstToken), nil
+		return group, nil
 	}
-	if err := node.SetComment(firstToken); err != nil {
+	if err := node.SetComment(group); err != nil {
 		return nil, errors.Wrapf(err, "failed to set comment token to node")
 	}
 	return node, nil
diff --git a/path.go b/path.go
index 2ba88ba..025a5e2 100644
--- a/path.go
+++ b/path.go
@@ -638,7 +638,7 @@ func (n *recursiveNode) String() string {
 }
 
 func (n *recursiveNode) filterNode(node ast.Node) (*ast.SequenceNode, error) {
-	sequence := &ast.SequenceNode{}
+	sequence := &ast.SequenceNode{BaseNode: &ast.BaseNode{}}
 	switch typedNode := node.(type) {
 	case *ast.MappingNode:
 		for _, value := range typedNode.Values {


Test patch:
diff --git a/parser/parser_test.go b/parser/parser_test.go
index fb7526d..c579ccd 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -697,6 +697,36 @@ f: g # commentJ
 			yaml: `
 a: &x b # commentA
 c: *x # commentB
+`,
+		},
+		{
+			name: "multiline",
+			yaml: `
+# foo comment
+# foo comment2
+foo: # map key comment
+  # bar above comment
+  # bar above comment2
+  bar: 10 # comment for bar
+  # baz above comment
+  # baz above comment2
+  baz: bbbb # comment for baz
+  piyo: # sequence key comment
+  # sequence1 above comment 1
+  # sequence1 above comment 2
+  - sequence1 # sequence1
+  # sequence2 above comment 1
+  # sequence2 above comment 2
+  - sequence2 # sequence2
+  # sequence3 above comment 1
+  # sequence3 above comment 2
+  - false # sequence3
+# foo2 comment
+# foo2 comment2
+foo2: &anchor text # anchor comment
+# foo3 comment
+# foo3 comment2
+foo3: *anchor # alias comment
 `,
 		},
 	}
@@ -706,9 +736,9 @@ c: *x # commentB
 			if err != nil {
 				t.Fatalf("%+v", err)
 			}
-			var v Visitor
-			for _, doc := range f.Docs {
-				ast.Walk(&v, doc.Body)
+			got := "\n" + f.String() + "\n"
+			if test.yaml != got {
+				t.Fatalf("expected:%s\ngot:%s", test.yaml, got)
 			}
 		})
 	}
@@ -721,9 +751,5 @@ func (v *Visitor) Visit(node ast.Node) ast.Visitor {
 	tk := node.GetToken()
 	tk.Prev = nil
 	tk.Next = nil
-	if comment := node.GetComment(); comment != nil {
-		comment.Prev = nil
-		comment.Next = nil
-	}
 	return v
 }