Repository: goccy/go-yaml (go)

Issue:
String method call panics on empty document node
**Describe the bug**

The `ast.DocumentNode` panics on empty document, example program:

https://go.dev/play/p/YmrzDKhtFNp

```
package main

import (
	"fmt"

	"github.com/goccy/go-yaml/lexer"
	"github.com/goccy/go-yaml/parser"
)

func main() {
	f, err := parser.Parse(lexer.Tokenize(""), 0)
	if err != nil {
		panic(err)
	}
	fmt.Println(f.String())
}
```

The above program fails with panic:

```
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x58 pc=0x4ada54]

goroutine 1 [running]:
github.com/goccy/go-yaml/ast.(*DocumentNode).String(0xc0000162a0)
	/tmp/gopath1302303734/pkg/mod/github.com/goccy/go-yaml@v1.15.5/ast/ast.go:578 +0xb4
github.com/goccy/go-yaml/ast.(*File).String(0x0?)
	/tmp/gopath1302303734/pkg/mod/github.com/goccy/go-yaml@v1.15.5/ast/ast.go:535 +0x78
main.main()
	/tmp/sandbox432899830/prog.go:15 +0x34
```

This seems to be an regression introduced in v1.15.0 as the above same program works in v1.14.3 : 

https://go.dev/play/p/VubIuMgDfYQ

I believe this is because now the body of the document node can be nil due to the new implementation in here:

https://github.com/goccy/go-yaml/blob/339908482797d14c30d665bb8af25304f4e613a9/parser/parser.go#L107-L109

while the `DocumentNode.String` method skips the nil check on body:

https://github.com/goccy/go-yaml/blob/339908482797d14c30d665bb8af25304f4e613a9/ast/ast.go#L578

**To Reproduce**

Please provide a minimum yaml content that can be reproduced.
We are more than happy to use [Go Playground](https://go.dev/play)

**Expected behavior**

`.String()` should not panic for an empty document node 

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Version Variables**
 - Go version: [e.g. 1.21 ]
 - go-yaml's Version: [e.g. v1.11.1 ]

**Additional context**
Add any other context about the problem here.


Pull request:
fix: empty document should not panic on String()
fixes #629

Before submitting your PR, please confirm the following.

- [x] Describe the purpose for which you created this PR.  
- [x] Create test code that corresponds to the modification

Patch:
diff --git a/ast/ast.go b/ast/ast.go
index 7f34227..d9607fa 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -576,7 +576,9 @@ func (d *DocumentNode) String() string {
 	if d.Start != nil {
 		doc = append(doc, d.Start.Value)
 	}
-	doc = append(doc, d.Body.String())
+	if d.Body != nil {
+		doc = append(doc, d.Body.String())
+	}
 	if d.End != nil {
 		doc = append(doc, d.End.Value)
 	}
diff --git a/decode.go b/decode.go
index fda5a6d..779ecb5 100644
--- a/decode.go
+++ b/decode.go
@@ -1980,6 +1980,9 @@ func (d *Decoder) decodeInit() error {
 		return err
 	}
 	d.parsedFile = file
+	for k := range d.toCommentMap {
+		delete(d.toCommentMap, k)
+	}
 	return nil
 }
 
diff --git a/path.go b/path.go
index 974fc93..4d2cbba 100644
--- a/path.go
+++ b/path.go
@@ -629,7 +629,7 @@ func (n *indexNode) filter(node ast.Node) (ast.Node, error) {
 	}
 	sequence, _ := node.(*ast.SequenceNode)
 	if n.selector >= uint(len(sequence.Values)) {
-		return nil, fmt.Errorf("expected index is %d. but got sequences has %d items: %w", n.selector, sequence.Values, ErrInvalidQuery)
+		return nil, fmt.Errorf("expected index is %d. but got sequences has %d items: %w", n.selector, len(sequence.Values), ErrInvalidQuery)
 	}
 	value := sequence.Values[n.selector]
 	if n.child == nil {
@@ -648,7 +648,7 @@ func (n *indexNode) replace(node ast.Node, target ast.Node) error {
 	}
 	sequence, _ := node.(*ast.SequenceNode)
 	if n.selector >= uint(len(sequence.Values)) {
-		return fmt.Errorf("expected index is %d. but got sequences has %d items: %w", n.selector, sequence.Values, ErrInvalidQuery)
+		return fmt.Errorf("expected index is %d. but got sequences has %d items: %w", n.selector, len(sequence.Values), ErrInvalidQuery)
 	}
 	if n.child == nil {
 		if err := sequence.Replace(int(n.selector), target); err != nil {


Test patch:
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 8a80925..487bfe1 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -16,6 +16,7 @@ import (
 
 func TestParser(t *testing.T) {
 	sources := []string{
+		"",
 		"null\n",
 		"0_",
 		"{}\n",
@@ -144,12 +145,52 @@ foo: zzz
 `,
 	}
 	for _, src := range sources {
-		if _, err := parser.Parse(lexer.Tokenize(src), 0); err != nil {
+		f, err := parser.Parse(lexer.Tokenize(src), 0)
+		if err != nil {
 			t.Fatalf("parse error: source [%s]: %+v", src, err)
 		}
+		_ = f.String() // ensure no panic
 	}
 }
 
+func TestParseEmptyDocument(t *testing.T) {
+	t.Run("empty document", func(t *testing.T) {
+		f, err := parser.ParseBytes([]byte(""), parser.ParseComments)
+		if err != nil {
+			t.Fatal(err)
+		}
+		got := f.String()
+		expected := "\n"
+		if got != expected {
+			t.Fatalf("failed to parse comment:\nexpected:\n%q\ngot:\n%q", expected, got)
+		}
+	})
+
+	t.Run("empty document with comment (parse comment = off)", func(t *testing.T) {
+		f, err := parser.ParseBytes([]byte("# comment"), 0)
+		if err != nil {
+			t.Fatal(err)
+		}
+		got := f.String()
+		expected := "\n"
+		if got != expected {
+			t.Fatalf("failed to parse comment:\nexpected:\n%q\ngot:\n%q", expected, got)
+		}
+	})
+
+	t.Run("empty document with comment (parse comment = on)", func(t *testing.T) {
+		f, err := parser.ParseBytes([]byte("# comment"), parser.ParseComments)
+		if err != nil {
+			t.Fatal(err)
+		}
+		got := f.String()
+		expected := "# comment\n"
+		if got != expected {
+			t.Fatalf("failed to parse comment:\nexpected:\n%q\ngot:\n%q", expected, got)
+		}
+	})
+}
+
 func TestParseComplicatedDocument(t *testing.T) {
 	tests := []struct {
 		source string