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