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