061b7e5f358c283430a11701eb7c8602c4599ddd
[libreriscv.git] / openpower / power_trans_ops_copy_from_PO59_table.py
1 #!/usr/bin/env python3
2 from pathlib import Path
3 import re
4 from typing import Iterable
5
6 FILENAME = Path("openpower/power_trans_ops.mdwn")
7 NEW_FILENAME = FILENAME.with_suffix(".new.mdwn")
8 OLD_FILENAME = FILENAME.with_suffix(".old.mdwn")
9
10 PO_59_HEADER = "## PO=59 XO=10--011--"
11 MNEMONIC_COLUMN_NAME = "opcode"
12 XO_COLUMN_NAME = "Major 59 and 63"
13
14 # TODO: copy PO=59 table to PO=63 table
15
16 class LineReader:
17 def __init__(self, lines):
18 # type: (list[str]) -> None
19 self.__next_line_index = 0
20 self.__lines = lines
21
22 def read(self):
23 if self.__next_line_index == len(self.__lines):
24 self.__next_line_index += 1
25 return None, self.__next_line_index
26 assert self.__next_line_index < len(self.__lines), \
27 "read past end-of-file"
28 line = self.__lines[self.__next_line_index].rstrip()
29 self.__next_line_index += 1
30 return line, self.__next_line_index
31
32
33 def process(lr):
34 # type: (LineReader) -> Iterable[str]
35
36 line, lineno = lr.read()
37
38 mnemonic_to_xo_map = {} # type: dict[str, str]
39
40 def parse_table_separator_line(len_line_parts):
41 # type: (int) -> Iterable[str]
42 nonlocal line, lineno
43
44 assert line is not None \
45 and line.startswith('|') \
46 and line.endswith('|') \
47 and len(line.split('|')) == len_line_parts \
48 and line.strip(" |-") == "", "invalid table separator line"
49
50 yield line
51 line, lineno = lr.read()
52
53 assert line is not None and line != "", "empty table"
54
55 def parse_mnemonic_to_opcode_map():
56 # type: () -> Iterable[str]
57 nonlocal line, lineno, mnemonic_to_xo_map
58
59 while line != PO_59_HEADER:
60 assert line is not None, "missing PO=59 header"
61 yield line
62 line, lineno = lr.read()
63
64 yield line
65 line, lineno = lr.read()
66
67 while line is not None and not line.startswith(("#", "|")):
68 yield line
69 line, lineno = lr.read()
70
71 assert line is not None and line.startswith(
72 "| XO LSB half &#x2192; <br/> XO MSB half &#x2193; |"), \
73 "can't find PO=59 table"
74 line_parts = line.split('|')
75 len_line_parts = len(line_parts)
76 assert line_parts[-1] == "", "invalid PO=59 table top row"
77 columns = [] # type: list[str]
78 columns_range = range(2, len_line_parts - 1)
79 for i in columns_range:
80 column = line_parts[i].strip()
81 if column.startswith('`') and column.endswith('`'):
82 column = column[1:-1].strip()
83 assert column.lstrip(" 01") == "", (f"invalid table top row "
84 f"contents -- must be a "
85 f"binary string: {column}")
86 columns.append(column)
87
88 yield line
89 line, lineno = lr.read()
90
91 yield from parse_table_separator_line(len_line_parts)
92
93 while line is not None and line != "":
94 line_parts = line.split('|')
95 assert line.startswith('|') and line.endswith('|'), \
96 "invalid table line, must start and end with |"
97 assert len(line_parts) == len_line_parts, (
98 f"invalid table line, wrong part count: found "
99 f"{len(line_parts)} expected {len_line_parts}")
100 row = line_parts[1].strip()
101 if row.startswith('`') and row.endswith('`'):
102 row = row[1:-1].strip()
103 assert row.lstrip(" 01") == "", (f"invalid table line header-cell "
104 f"contents -- must be a "
105 f"binary string: {row}")
106 for i, column in zip(columns_range, columns):
107 cell = line_parts[i]
108 match = re.fullmatch(
109 r" *<small> *` *(?P<xo>[01][01 ]*[01]) *` *</small>"
110 r" *<br/> *(?P<mnemonic>[a-zA-Z0-9_.]+)?"
111 r"(?(mnemonic)|(?:\([a-zA-Z0-9_.]+\)|\*\*TBD\*\*|))"
112 r"(?: *\(draft\))? *", cell)
113 assert match is not None, f"invalid table cell: {cell!r}"
114 xo, mnemonic = match.group("xo", "mnemonic")
115 shrunk_xo = xo.replace(" ", "")
116 expected_xo = (row + column).replace(" ", "")
117 assert shrunk_xo == expected_xo, \
118 f"incorrect XO: found {shrunk_xo} expected {expected_xo}"
119 if mnemonic is None:
120 continue
121 assert mnemonic.endswith('s'), \
122 f"PO=59 fptrans mnemonic must end in `s`: {mnemonic}"
123 assert mnemonic not in mnemonic_to_xo_map, (
124 f"duplicate mnemonic: {mnemonic} -- has opcode "
125 f"{xo} and {mnemonic_to_xo_map[mnemonic]}")
126
127 mnemonic_to_xo_map[mnemonic] = xo
128
129 yield line
130 line, lineno = lr.read()
131
132 def skip_table():
133 # type: () -> Iterable[str]
134 nonlocal line, lineno
135
136 assert line is not None \
137 and line.startswith("|") and line.endswith('|'), \
138 "invalid table header"
139 line_parts = line.split("|")
140 len_line_parts = len(line_parts)
141 assert len_line_parts >= 3, "invalid table header"
142
143 yield line
144 line, lineno = lr.read()
145
146 yield from parse_table_separator_line(len_line_parts)
147 while line is not None and line != "":
148 line_parts = line.split('|')
149 assert line.startswith('|') and line.endswith('|'), \
150 "invalid table line, must start and end with |"
151 assert len(line_parts) == len_line_parts, (
152 f"invalid table line, wrong part count: found "
153 f"{len(line_parts)} expected {len_line_parts}")
154
155 yield line
156 line, lineno = lr.read()
157
158 def handle_table():
159 # type: () -> Iterable[str]
160 nonlocal line, lineno
161
162 assert line is not None \
163 and line.startswith("|") and line.endswith('|'), \
164 "invalid table header"
165 line_parts = line.split("|")
166 len_line_parts = len(line_parts)
167 assert len_line_parts >= 3, "invalid table header"
168 mnemonic_index = None
169 xo_index = None
170 xo_column_width = 0
171 for i, column in enumerate(line_parts):
172 column_width = len(column) # should use wcswidth here
173 column = column.strip()
174 if column == MNEMONIC_COLUMN_NAME:
175 assert mnemonic_index is None, \
176 f"two {MNEMONIC_COLUMN_NAME!r} columns in table " \
177 f"-- can't handle that"
178 mnemonic_index = i
179 if column == XO_COLUMN_NAME:
180 assert xo_index is None, \
181 f"two {XO_COLUMN_NAME!r} columns in table " \
182 f"-- can't handle that"
183 xo_index = i
184 xo_column_width = column_width
185 if mnemonic_index is None and xo_index is None:
186 # not an opcode table -- skip it
187 yield from skip_table()
188 return
189
190 assert mnemonic_index is not None, \
191 f"missing {MNEMONIC_COLUMN_NAME} column"
192 assert xo_index is not None, f"missing {XO_COLUMN_NAME} column"
193
194 yield line
195 line, lineno = lr.read()
196
197 yield from parse_table_separator_line(len_line_parts)
198 while line is not None and line != "":
199 line_parts = line.split('|')
200 assert line.startswith('|') and line.endswith('|'), \
201 "invalid table line, must start and end with |"
202 assert len(line_parts) == len_line_parts, (
203 f"invalid table line, wrong part count: found "
204 f"{len(line_parts)} expected {len_line_parts}")
205
206 mnemonic = line_parts[mnemonic_index].strip()
207 xo = line_parts[xo_index].strip()
208 if mnemonic.endswith("(s)") or mnemonic.endswith("[s]"):
209 mnemonic = mnemonic[:-3] + "s"
210 if mnemonic not in mnemonic_to_xo_map:
211 print(f"mnemonic not assigned an XO value: {mnemonic!r}")
212 elif xo == "":
213 xo = mnemonic_to_xo_map[mnemonic]
214 xo_width = len(xo) # should use wcswidth here
215 if xo_width < xo_column_width:
216 # should use wc_ljust here
217 xo = (" " + xo).ljust(xo_column_width)
218 line_parts[xo_index] = xo
219 else:
220 expected_xo = mnemonic_to_xo_map[mnemonic].replace(" ", "")
221 assert xo.replace(" ", "") == expected_xo, (
222 f"mismatch in {XO_COLUMN_NAME} column: expected "
223 f"{mnemonic_to_xo_map[mnemonic]} found {xo!r}")
224
225 yield '|'.join(line_parts)
226 line, lineno = lr.read()
227
228 try:
229 yield from parse_mnemonic_to_opcode_map()
230
231 print(mnemonic_to_xo_map)
232
233 while line is not None:
234 if line.startswith('|'):
235 yield from handle_table()
236 else:
237 yield line
238 line, lineno = lr.read()
239
240 except AssertionError as e:
241 raise AssertionError(f"\n{FILENAME}:{lineno}: error: {e}")
242
243
244 inp = FILENAME.read_text(encoding="utf-8")
245 output_lines = list(process(LineReader(inp.splitlines())))
246 if output_lines[-1] != "":
247 output_lines.append("") # ensure file ends with newline
248 NEW_FILENAME.write_text("\n".join(output_lines), encoding="utf-8")
249 FILENAME.replace(OLD_FILENAME)
250 NEW_FILENAME.rename(FILENAME)