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