(no commit message)
[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 if cell.strip() == "":
96 continue
97 match = re.fullmatch(
98 r" *<small> *` *(?P<xo>[01][01 ]*[01]) *` *</small>"
99 r" *<br/?> *(?P<mnemonic>[a-zA-Z0-9_.][a-zA-Z0-9_.()]*)?"
100 r"(?(mnemonic)|(?:\([a-zA-Z0-9_.()]+\)|\*\*TBD\*\*|))"
101 r"(?: *\(draft\))? *", cell)
102 assert match is not None, f"invalid table cell: {cell!r}"
103 xo, mnemonic = match.group("xo", "mnemonic")
104 shrunk_xo = xo.replace(" ", "")
105 expected_xo = (row + column).replace(" ", "")
106 assert shrunk_xo == expected_xo, \
107 f"incorrect XO: found {shrunk_xo} expected {expected_xo}"
108 if mnemonic is None:
109 continue
110 assert mnemonic.endswith('(s)'), \
111 f"PO=59/63 fptrans mnemonic must end in `(s)`: {mnemonic}"
112 assert mnemonic not in mnemonic_to_xo_map, (
113 f"duplicate mnemonic: {mnemonic} -- has opcode "
114 f"{xo} and {mnemonic_to_xo_map[mnemonic]}")
115
116 mnemonic_to_xo_map[mnemonic] = xo
117
118 yield line
119 line, lineno = lr.read()
120
121 while line == "":
122 yield line
123 line, lineno = lr.read()
124
125 def parse_mnemonic_to_opcode_map():
126 # type: () -> Iterable[str]
127 nonlocal line, lineno, mnemonic_to_xo_map
128
129 while line != PO_59_63_HEADER:
130 assert line is not None, "missing PO=59/63 header"
131 yield line
132 line, lineno = lr.read()
133
134 yield line
135 line, lineno = lr.read()
136
137 while line is not None and not line.startswith(("#", "|")):
138 yield line
139 line, lineno = lr.read()
140
141 for _ in range(2):
142 yield from parse_single_mnemonic_to_opcode_map()
143
144 def skip_table():
145 # type: () -> Iterable[str]
146 nonlocal line, lineno
147
148 assert line is not None \
149 and line.startswith("|") and line.endswith('|'), \
150 "invalid table header"
151 line_parts = line.split("|")
152 len_line_parts = len(line_parts)
153 assert len_line_parts >= 3, "invalid table header"
154
155 yield line
156 line, lineno = lr.read()
157
158 yield from parse_table_separator_line(len_line_parts)
159 while line is not None and line != "":
160 line_parts = line.split('|')
161 assert line.startswith('|') and line.endswith('|'), \
162 "invalid table line, must start and end with |"
163 assert len(line_parts) == len_line_parts, (
164 f"invalid table line, wrong part count: found "
165 f"{len(line_parts)} expected {len_line_parts}")
166
167 yield line
168 line, lineno = lr.read()
169
170 def handle_table():
171 # type: () -> Iterable[str]
172 nonlocal line, lineno
173
174 assert line is not None \
175 and line.startswith("|") and line.endswith('|'), \
176 "invalid table header"
177 line_parts = line.split("|")
178 len_line_parts = len(line_parts)
179 assert len_line_parts >= 3, "invalid table header"
180 mnemonic_index = None
181 xo_index = None
182 xo_column_width = 0
183 for i, column in enumerate(line_parts):
184 column_width = len(column) # should use wcswidth here
185 column = column.strip()
186 if column == MNEMONIC_COLUMN_NAME:
187 assert mnemonic_index is None, \
188 f"two {MNEMONIC_COLUMN_NAME!r} columns in table " \
189 f"-- can't handle that"
190 mnemonic_index = i
191 if column == XO_COLUMN_NAME:
192 assert xo_index is None, \
193 f"two {XO_COLUMN_NAME!r} columns in table " \
194 f"-- can't handle that"
195 xo_index = i
196 xo_column_width = column_width
197 if mnemonic_index is None and xo_index is None:
198 # not an opcode table -- skip it
199 yield from skip_table()
200 return
201
202 assert mnemonic_index is not None, \
203 f"missing {MNEMONIC_COLUMN_NAME} column"
204 assert xo_index is not None, f"missing {XO_COLUMN_NAME} column"
205
206 yield line
207 line, lineno = lr.read()
208
209 yield from parse_table_separator_line(len_line_parts)
210 while line is not None and line != "":
211 line_parts = line.split('|')
212 assert line.startswith('|') and line.endswith('|'), \
213 "invalid table line, must start and end with |"
214 assert len(line_parts) == len_line_parts, (
215 f"invalid table line, wrong part count: found "
216 f"{len(line_parts)} expected {len_line_parts}")
217
218 mnemonic = line_parts[mnemonic_index].strip()
219 xo = line_parts[xo_index].strip()
220 if mnemonic not in mnemonic_to_xo_map:
221 print(f"mnemonic not assigned an XO value: {mnemonic!r}")
222 elif xo == "":
223 xo = mnemonic_to_xo_map[mnemonic]
224 xo_width = len(xo) # should use wcswidth here
225 if xo_width < xo_column_width:
226 # should use wc_ljust here
227 xo = (" " + xo).ljust(xo_column_width)
228 line_parts[xo_index] = xo
229 else:
230 expected_xo = mnemonic_to_xo_map[mnemonic].replace(" ", "")
231 assert xo.replace(" ", "") == expected_xo, (
232 f"mismatch in {XO_COLUMN_NAME} column: expected "
233 f"{mnemonic_to_xo_map[mnemonic]} found {xo!r}")
234
235 yield '|'.join(line_parts)
236 line, lineno = lr.read()
237
238 try:
239 yield from parse_mnemonic_to_opcode_map()
240
241 print(mnemonic_to_xo_map)
242
243 while line is not None:
244 if line.startswith('|'):
245 yield from handle_table()
246 else:
247 yield line
248 line, lineno = lr.read()
249
250 except AssertionError as e:
251 raise AssertionError(f"\n{FILENAME}:{lineno}: error: {e}")
252
253
254 inp = FILENAME.read_text(encoding="utf-8")
255 output_lines = list(process(LineReader(inp.splitlines())))
256 if output_lines[-1] != "":
257 output_lines.append("") # ensure file ends with newline
258 NEW_FILENAME.write_text("\n".join(output_lines), encoding="utf-8")
259 FILENAME.replace(OLD_FILENAME)
260 NEW_FILENAME.rename(FILENAME)