golang.org/x/arch@v0.17.0/x86/xeddata/reader.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package xeddata 6 7 import ( 8 "errors" 9 "fmt" 10 "io" 11 "iter" 12 "regexp" 13 "strings" 14 ) 15 16 // Reader reads enc/dec-instruction objects from XED datafile. 17 type Reader struct { 18 r io.Reader 19 20 // Initialized on first call to Read 21 next func() (*Object, error, bool) 22 stop func() 23 err error 24 } 25 26 // NewReader returns a new Reader that reads from r. 27 func NewReader(r io.Reader) *Reader { 28 return &Reader{r: r} 29 } 30 31 // Read reads single XED instruction object from 32 // the stream backed by reader. 33 // 34 // If there is no data left to be read, 35 // returned error is io.EOF. 36 func (r *Reader) Read() (*Object, error) { 37 if r.err != nil { 38 return nil, r.err 39 } 40 if r.next == nil { 41 r.next, r.stop = iter.Pull2(readObjects(r.r)) 42 } 43 obj, err, end := r.next() 44 if end { 45 err = io.EOF 46 } 47 if err != nil { 48 r.stop() 49 r.err, r.next, r.stop = err, nil, nil 50 return nil, err 51 } 52 return obj, nil 53 } 54 55 // ReadAll reads all the remaining objects from r. 56 // A successful call returns err == nil, not err == io.EOF, 57 // just like csv.Reader.ReadAll(). 58 func (r *Reader) ReadAll() ([]*Object, error) { 59 var objects []*Object 60 for obj, err := range readObjects(r.r) { 61 if err != nil { 62 return objects, err 63 } 64 objects = append(objects, obj) 65 } 66 return objects, nil 67 } 68 69 // readObjects yields all of the objects from r. 70 func readObjects(r io.Reader) iter.Seq2[*Object, error] { 71 iterLines := readLines(r) 72 return func(yield func(*Object, error) bool) { 73 var blockPos Pos 74 var block []string // Reused on each iteration 75 var linePos []Pos 76 inBlock := false 77 for line, err := range iterLines { 78 if err != nil { 79 yield(nil, err) 80 return 81 } 82 if !inBlock { 83 inBlock = line.data[0] == '{' 84 blockPos = line.Pos 85 } else if line.data[0] == '}' { 86 inBlock = false 87 obj, err := parseObjectLines(blockPos, block, linePos) 88 if !yield(obj, err) { 89 return 90 } 91 block, linePos = block[:0], linePos[:0] 92 } else { 93 block = append(block, string(line.data)) 94 linePos = append(linePos, line.Pos) 95 } 96 } 97 if inBlock { 98 yield(nil, errors.New("no matching '}' found")) 99 } 100 } 101 } 102 103 // instLineRE matches valid XED object/inst line. 104 // It expects lines that are joined by '\' to be concatenated. 105 // 106 // The format can be described as: 107 // 108 // unquoted field name "[A-Z_]+" (captured) 109 // field value delimiter ":" 110 // field value string (captured) 111 var instLineRE = regexp.MustCompile(`^([A-Z_]+)\s*:\s*(.*)`) 112 113 // parseLines turns collected object lines into Object. 114 func parseObjectLines(blockPos Pos, lines []string, linePos []Pos) (*Object, error) { 115 o := &Object{} 116 o.Pos = blockPos 117 118 // Repeatable tokens. 119 // We can not assign them eagerly, because these fields 120 // are not guaranteed to follow strict order. 121 var ( 122 operands []string 123 iforms []string 124 patterns []string 125 poses []Pos 126 ) 127 128 for i, l := range lines { 129 l = strings.TrimLeft(l, " ") 130 if l[0] == '#' { // Skip comment lines. 131 continue 132 } 133 m := instLineRE.FindStringSubmatch(l) 134 if len(m) == 0 { 135 return nil, fmt.Errorf("malformed line: %s", l) 136 } 137 key, val := m[1], m[2] 138 val = strings.TrimSpace(val) 139 140 switch key { 141 case "ICLASS": 142 o.Iclass = val 143 case "DISASM": 144 o.Disasm = val 145 case "DISASM_INTEL": 146 o.DisasmIntel = val 147 case "DISASM_ATTSV": 148 o.DisasmATTSV = val 149 case "ATTRIBUTES": 150 o.Attributes = val 151 case "UNAME": 152 o.Uname = val 153 case "CPL": 154 o.CPL = val 155 case "CATEGORY": 156 o.Category = val 157 case "EXTENSION": 158 o.Extension = val 159 case "EXCEPTIONS": 160 o.Exceptions = val 161 case "ISA_SET": 162 o.ISASet = val 163 case "FLAGS": 164 o.Flags = val 165 case "COMMENT": 166 o.Comment = val 167 case "VERSION": 168 o.Version = val 169 case "REAL_OPCODE": 170 o.RealOpcode = val 171 172 case "OPERANDS": 173 operands = append(operands, val) 174 case "PATTERN": 175 patterns = append(patterns, val) 176 poses = append(poses, linePos[i]) 177 case "IFORM": 178 iforms = append(iforms, val) 179 180 default: 181 // Being strict about unknown field names gives a nice 182 // XED file validation diagnostics. 183 // Also defends against typos in test files. 184 return nil, fmt.Errorf("unknown key token: %s", key) 185 } 186 } 187 188 if len(operands) != len(patterns) { 189 return nil, fmt.Errorf("%s: OPERANDS and PATTERN lines mismatch", o.Opcode()) 190 } 191 192 insts := make([]*Inst, len(operands)) 193 for i := range operands { 194 insts[i] = &Inst{ 195 Object: o, 196 Index: i, 197 Pattern: patterns[i], 198 Pos: poses[i], 199 Operands: operands[i], 200 } 201 // There can be less IFORMs than insts. 202 if i < len(iforms) { 203 insts[i].Iform = iforms[i] 204 } 205 } 206 o.Insts = insts 207 208 return o, nil 209 }