Repository: goccy/go-yaml (go)

Issue:
Parsing multi-line values
I need to parse YAML files in a way that gives me a line number of each element. I started by using go-yaml/yaml for this but quickly realised that it's difficult to get accurate position information from it as it never gives me the raw original value from the file, only decoded value, which makes it difficult to tell how many lines is given value occupying.
I found this project, which seems great fit for it, and after poking around the API docs I think I figure out how to use it for it, but when I try to parse some multi-line with `parser.ParseBytes()` I see that some values span across multiple `DocumentNode`.

Here's an example: https://play.golang.org/p/5_bGwq7Md_H

```
package main

import (
	"fmt"

	"github.com/goccy/go-yaml/parser"
)

func main() {
	content := `
- key1: val
  key2:
    (
      foo
      +
      bar
    )
`
	f, err := parser.ParseBytes([]byte(content), parser.ParseComments)
	if err != nil {
		panic(err)
	}

	for _, doc := range f.Docs {
		fmt.Printf("doc: [%s]\n", doc.Body)
	}
}
```

Gives me 2 `DocumentNode` instances, once for `( foo + bar` and one for trailing `)`.
Am I doing something wrong here or is it a bug?

Pull request:
Fix indentState logic for multi-line value
fix #218

Patch:
diff --git a/scanner/scanner.go b/scanner/scanner.go
index 7082c5a..1e09190 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -128,27 +128,16 @@ func (s *Scanner) newLineCount(src []rune) int {
 	return cnt
 }
 
-func (s *Scanner) updateIndent(ctx *Context, c rune) {
-	if s.isFirstCharAtLine && s.isNewLineChar(c) && ctx.isDocument() {
-		return
-	}
-	if s.isFirstCharAtLine && c == ' ' {
-		s.indentNum++
-		return
-	}
-	if !s.isFirstCharAtLine {
-		s.indentState = IndentStateKeep
-		return
-	}
-
+func (s *Scanner) updateIndentState(ctx *Context) {
+	indentNumBasedIndentState := s.indentState
 	if s.prevIndentNum < s.indentNum {
 		s.indentLevel = s.prevIndentLevel + 1
-		s.indentState = IndentStateUp
+		indentNumBasedIndentState = IndentStateUp
 	} else if s.prevIndentNum == s.indentNum {
 		s.indentLevel = s.prevIndentLevel
-		s.indentState = IndentStateEqual
+		indentNumBasedIndentState = IndentStateEqual
 	} else {
-		s.indentState = IndentStateDown
+		indentNumBasedIndentState = IndentStateDown
 		if s.prevIndentLevel > 0 {
 			s.indentLevel = s.prevIndentLevel - 1
 		}
@@ -157,18 +146,48 @@ func (s *Scanner) updateIndent(ctx *Context, c rune) {
 	if s.prevIndentColumn > 0 {
 		if s.prevIndentColumn < s.column {
 			s.indentState = IndentStateUp
-		} else if s.prevIndentColumn == s.column {
-			s.indentState = IndentStateEqual
-		} else {
+		} else if s.prevIndentColumn != s.column || indentNumBasedIndentState != IndentStateEqual {
+			// The following case ( current position is 'd' ), some variables becomes like here
+			// - prevIndentColumn: 1 of 'a'
+			// - indentNumBasedIndentState: IndentStateDown because d's indentNum(1) is less than c's indentNum(3).
+			// Therefore, s.prevIndentColumn(1) == s.column(1) is true, but we want to treat this as IndentStateDown.
+			// So, we look also current indentState value by the above prevIndentNum based logic, and determins finally indentState.
+			// ---
+			// a:
+			//   b
+			//   c
+			// d: e
+			// ^
 			s.indentState = IndentStateDown
+		} else {
+			s.indentState = IndentStateEqual
 		}
+	} else {
+		s.indentState = indentNumBasedIndentState
 	}
+}
+
+func (s *Scanner) updateIndent(ctx *Context, c rune) {
+	if s.isFirstCharAtLine && s.isNewLineChar(c) && ctx.isDocument() {
+		return
+	}
+	if s.isFirstCharAtLine && c == ' ' {
+		s.indentNum++
+		return
+	}
+	if !s.isFirstCharAtLine {
+		s.indentState = IndentStateKeep
+		return
+	}
+	s.updateIndentState(ctx)
 	s.isFirstCharAtLine = false
 	if s.isNeededKeepPreviousIndentNum(ctx, c) {
 		return
 	}
+	if s.indentState != IndentStateUp {
+		s.prevIndentColumn = 0
+	}
 	s.prevIndentNum = s.indentNum
-	s.prevIndentColumn = 0
 	s.prevIndentLevel = s.indentLevel
 }
 


Test patch:
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 9439d6f..595e0a3 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -547,25 +547,42 @@ b: c
 a: |+
   value
 b: c
+`,
+		},
+		{
+			`
+- key1: val
+  key2:
+    (
+      foo
+      +
+      bar
+    )
+`,
+			`
+- key1: val
+  key2: ( foo + bar )
 `,
 		},
 	}
 
 	for _, test := range tests {
-		tokens := lexer.Tokenize(test.source)
-		f, err := parser.Parse(tokens, 0)
-		if err != nil {
-			t.Fatalf("%+v", err)
-		}
-		var v Visitor
-		for _, doc := range f.Docs {
-			ast.Walk(&v, doc.Body)
-		}
-		expect := fmt.Sprintf("\n%+v\n", f)
-		if test.expect != expect {
-			tokens.Dump()
-			t.Fatalf("unexpected output: [%s] != [%s]", test.expect, expect)
-		}
+		t.Run(test.source, func(t *testing.T) {
+			tokens := lexer.Tokenize(test.source)
+			f, err := parser.Parse(tokens, 0)
+			if err != nil {
+				t.Fatalf("%+v", err)
+			}
+			var v Visitor
+			for _, doc := range f.Docs {
+				ast.Walk(&v, doc.Body)
+			}
+			expect := fmt.Sprintf("\n%+v\n", f)
+			if test.expect != expect {
+				tokens.Dump()
+				t.Fatalf("unexpected output: [%s] != [%s]", test.expect, expect)
+			}
+		})
 	}
 }