x86/xeddata: add Pos to Object and Inst

Change-Id: Ib9ad5e2c4bbd005b7fad15b7d0dc8943f2747689
Reviewed-on: https://go-review.googlesource.com/c/arch/+/656239
Auto-Submit: Austin Clements <austin@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/x86/xeddata/object.go b/x86/xeddata/object.go
index 4a73973..662aa69 100644
--- a/x86/xeddata/object.go
+++ b/x86/xeddata/object.go
@@ -21,6 +21,9 @@
 // Object contains multiple Inst elements that represent concrete
 // instruction with encoding pattern and operands description.
 type Object struct {
+	// Pos is the file position of the start of this object.
+	Pos Pos
+
 	// Iclass is instruction class name (opcode).
 	// Iclass alone is not enough to uniquely identify machine instructions.
 	// Example: "PSRLW".
@@ -128,6 +131,9 @@
 	// Inst objects.
 	*Object
 
+	// Pos is the file position of this Inst's PATTERN.
+	Pos Pos
+
 	// Index is the position inside XED object.
 	// Object.Insts[Index] returns this inst.
 	Index int
diff --git a/x86/xeddata/reader.go b/x86/xeddata/reader.go
index c1a0aa7..02fd9c8 100644
--- a/x86/xeddata/reader.go
+++ b/x86/xeddata/reader.go
@@ -70,7 +70,9 @@
 func readObjects(r io.Reader) iter.Seq2[*Object, error] {
 	iterLines := readLines(r)
 	return func(yield func(*Object, error) bool) {
+		var blockPos Pos
 		var block []string // Reused on each iteration
+		var linePos []Pos
 		inBlock := false
 		for line, err := range iterLines {
 			if err != nil {
@@ -79,15 +81,17 @@
 			}
 			if !inBlock {
 				inBlock = line.data[0] == '{'
+				blockPos = line.Pos
 			} else if line.data[0] == '}' {
 				inBlock = false
-				obj, err := parseObjectLines(block)
+				obj, err := parseObjectLines(blockPos, block, linePos)
 				if !yield(obj, err) {
 					return
 				}
-				block = block[:0]
+				block, linePos = block[:0], linePos[:0]
 			} else {
 				block = append(block, string(line.data))
+				linePos = append(linePos, line.Pos)
 			}
 		}
 		if inBlock {
@@ -107,8 +111,9 @@
 var instLineRE = regexp.MustCompile(`^([A-Z_]+)\s*:\s*(.*)`)
 
 // parseLines turns collected object lines into Object.
-func parseObjectLines(lines []string) (*Object, error) {
+func parseObjectLines(blockPos Pos, lines []string, linePos []Pos) (*Object, error) {
 	o := &Object{}
+	o.Pos = blockPos
 
 	// Repeatable tokens.
 	// We can not assign them eagerly, because these fields
@@ -117,9 +122,10 @@
 		operands []string
 		iforms   []string
 		patterns []string
+		poses    []Pos
 	)
 
-	for _, l := range lines {
+	for i, l := range lines {
 		l = strings.TrimLeft(l, " ")
 		if l[0] == '#' { // Skip comment lines.
 			continue
@@ -167,6 +173,7 @@
 			operands = append(operands, val)
 		case "PATTERN":
 			patterns = append(patterns, val)
+			poses = append(poses, linePos[i])
 		case "IFORM":
 			iforms = append(iforms, val)
 
@@ -188,6 +195,7 @@
 			Object:   o,
 			Index:    i,
 			Pattern:  patterns[i],
+			Pos:      poses[i],
 			Operands: operands[i],
 		}
 		// There can be less IFORMs than insts.
diff --git a/x86/xeddata/xeddata_test.go b/x86/xeddata/xeddata_test.go
index fc98d86..8b64be4 100644
--- a/x86/xeddata/xeddata_test.go
+++ b/x86/xeddata/xeddata_test.go
@@ -470,6 +470,42 @@
 	}
 }
 
+func TestReaderPos(t *testing.T) {
+	const data = `# Comment
+{
+ICLASS: iclass1
+DISASM: disasm1
+
+PATTERN: pat1 pat1
+OPERANDS: ops1 ops1
+}`
+	r := NewReader(namedReader{strings.NewReader(data), "test"})
+	objects, err := r.ReadAll()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if want := "test:2"; objects[0].Pos.String() != want {
+		t.Errorf("object Pos: got %q, want %q", objects[0].Pos, want)
+	}
+	if want := "test:6"; objects[0].Insts[0].Pos.String() != want {
+		t.Errorf("inst Pos: got %q, want %q", objects[0].Insts[0].Pos, want)
+	}
+}
+
+type namedReader struct {
+	r    io.Reader
+	name string
+}
+
+func (n namedReader) Read(p []byte) (int, error) {
+	return n.r.Read(p)
+}
+
+func (n namedReader) Name() string {
+	return n.name
+}
+
 func TestMacroExpand(t *testing.T) {
 	tests := [...]struct {
 		input  string