Wed, 31 Dec 2014 07:53:36 +0100
Correct small whitespace inconsistency, lost while renaming variables.
1 #!/usr/bin/python -B
3 """ Usage: make_opcode_doc.py PATH_TO_MOZILLA_CENTRAL
5 This script generates SpiderMonkey bytecode documentation
6 from js/src/vm/Opcodes.h and js/src/vm/Xdr.h.
8 Output is written to stdout and should be pasted into the following
9 MDN page:
10 https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
11 """
13 from __future__ import print_function
14 import re
15 import sys
16 from xml.sax.saxutils import escape
18 SOURCE_BASE = 'http://mxr.mozilla.org/mozilla-central/source'
20 def error(message):
21 print("Error: {message}".format(message=message), file=sys.stderr)
22 #sys.exit(1) # uncomment when all opcodes documented
24 def get_xdr_version(dir):
25 version_pat = re.compile('XDR_BYTECODE_VERSION = uint32_t\(0xb973c0de - (\d+)\);')
27 version = ''
28 with open('{dir}/js/src/vm/Xdr.h'.format(dir=dir), 'r') as f:
29 data = f.read()
31 m = version_pat.search(data)
32 if m:
33 version = int(m.group(1))
35 return version
37 quoted_pat = re.compile(r"([^A-Za-z0-9]|^)'([^']+)'")
38 js_pat = re.compile(r"([^A-Za-z0-9]|^)(JS[A-Z0-9_\*]+)")
39 def codify(text):
40 text = re.sub(quoted_pat, '\\1<code>\\2</code>', text)
41 text = re.sub(js_pat, '\\1<code>\\2</code>', text)
43 return text
45 space_star_space_pat = re.compile('^\s*\* ?', re.M)
46 def get_comment_body(comment):
47 return re.sub(space_star_space_pat, '', comment).split('\n')
49 def parse_index(comment):
50 index = []
51 current_types = None
52 category_name = ''
53 category_pat = re.compile('\[([^\]]+)\]')
54 for line in get_comment_body(comment):
55 m = category_pat.search(line)
56 if m:
57 category_name = m.group(1)
58 if category_name == 'Index':
59 continue
60 current_types = []
61 index.append((category_name, current_types))
62 else:
63 type_name = line.strip()
64 if type_name and current_types is not None:
65 current_types.append((type_name, []))
67 return index
69 class OpcodeInfo:
70 def __init__(self):
71 self.name = ''
72 self.value = ''
73 self.length = ''
74 self.length_override = ''
75 self.nuses = ''
76 self.nuses_override = ''
77 self.ndefs = ''
78 self.ndefs_override = ''
79 self.flags = ''
80 self.operands = ''
81 self.stack_uses = ''
82 self.stack_defs = ''
84 self.desc = ''
86 self.category_name = ''
87 self.type_name = ''
89 self.group = []
90 self.sort_key = ''
92 def find_by_name(list, name):
93 for (n, body) in list:
94 if n == name:
95 return body
97 return None
99 def add_to_index(index, opcode):
100 types = find_by_name(index, opcode.category_name)
101 if types is None:
102 error("Category is not listed in index: "
103 "{name}".format(name=opcode.category_name))
104 opcodes = find_by_name(types, opcode.type_name)
105 if opcodes is None:
106 if opcode.type_name:
107 error("Type is not listed in {category}: "
108 "{name}".format(category=opcode.category_name,
109 name=opcode.type_name))
110 types.append((opcode.type_name, [opcode]))
111 return
113 opcodes.append(opcode)
115 def format_desc(descs):
116 current_type = ''
117 desc = ''
118 for (type, line) in descs:
119 if type != current_type:
120 if current_type:
121 desc += '</{name}>\n'.format(name=current_type)
122 current_type = type
123 if type:
124 desc += '<{name}>'.format(name=current_type)
125 if current_type:
126 desc += line + "\n"
127 if current_type:
128 desc += '</{name}>'.format(name=current_type)
130 return desc
132 tag_pat = re.compile('^\s*[A-Za-z]+:\s*|\s*$')
133 def get_tag_value(line):
134 return re.sub(tag_pat, '', line)
136 def get_opcodes(dir):
137 iter_pat = re.compile(r"/\*(.*?)\*/" # either a documentation comment...
138 r"|"
139 r"macro\(" # or a macro(...) call
140 r"([^,]+),\s*" # op
141 r"([0-9]+),\s*" # val
142 r"[^,]+,\s*" # name
143 r"[^,]+,\s*" # image
144 r"([0-9\-]+),\s*" # length
145 r"([0-9\-]+),\s*" # nuses
146 r"([0-9\-]+),\s*" # ndefs
147 r"([^\)]+)" # format
148 r"\)", re.S)
149 stack_pat = re.compile('^(.*?)\s*=>\s*(.*?)$')
151 index = []
153 opcode = OpcodeInfo()
154 merged = opcode
156 with open('{dir}/js/src/vm/Opcodes.h'.format(dir=dir), 'r') as f:
157 data = f.read()
159 for m in re.finditer(iter_pat, data):
160 comment = m.group(1)
161 name = m.group(2)
163 if comment:
164 if '[Index]' in comment:
165 index = parse_index(comment)
166 continue
168 if 'Operands:' not in comment:
169 continue
171 state = 'desc'
172 stack = ''
173 descs = []
175 for line in get_comment_body(comment):
176 if line.startswith(' Category:'):
177 state = 'category'
178 opcode.category_name = get_tag_value(line)
179 elif line.startswith(' Type:'):
180 state = 'type'
181 opcode.type_name = get_tag_value(line)
182 elif line.startswith(' Operands:'):
183 state = 'operands'
184 opcode.operands = get_tag_value(line)
185 elif line.startswith(' Stack:'):
186 state = 'stack'
187 stack = get_tag_value(line)
188 elif line.startswith(' len:'):
189 state = 'len'
190 opcode.length_override = get_tag_value(line)
191 elif line.startswith(' nuses:'):
192 state = 'nuses'
193 opcode.nuses_override = get_tag_value(line)
194 elif line.startswith(' ndefs:'):
195 state = 'ndefs'
196 opcode.ndefs_override = get_tag_value(line)
197 elif state == 'desc':
198 if line.startswith(' '):
199 descs.append(('pre', escape(line[1:])))
200 else:
201 line = line.strip()
202 if line == '':
203 descs.append(('', line))
204 else:
205 descs.append(('p', codify(escape(line))))
206 elif line.startswith(' '):
207 if state == 'operands':
208 opcode.operands += line.strip()
209 elif state == 'stack':
210 stack += line.strip()
211 elif state == 'len':
212 opcode.length_override += line.strip()
213 elif state == 'nuses':
214 opcode.nuses_override += line.strip()
215 elif state == 'ndefs':
216 opcode.ndefs_override += line.strip()
218 opcode.desc = format_desc(descs)
220 m2 = stack_pat.search(stack)
221 if m2:
222 opcode.stack_uses = m2.group(1)
223 opcode.stack_defs = m2.group(2)
225 merged = opcode
226 elif name and not name.startswith('JSOP_UNUSED'):
227 opcode.name = name
228 opcode.value = int(m.group(3))
229 opcode.length = m.group(4)
230 opcode.nuses = m.group(5)
231 opcode.ndefs = m.group(6)
233 flags = []
234 for flag in m.group(7).split('|'):
235 if flag != 'JOF_BYTE':
236 flags.append(flag.replace('JOF_', ''))
237 opcode.flags = ', '.join(flags)
239 if merged == opcode:
240 opcode.sort_key = opcode.name
241 if opcode.category_name == '':
242 error("Category is not specified for "
243 "{name}".format(name=opcode.name))
244 add_to_index(index, opcode)
245 else:
246 if merged.length != opcode.length:
247 error("length should be same for merged section: "
248 "{value1}({name1}) != "
249 "{value2}({name2})".format(name1=merged.name,
250 value1=merged.length,
251 name2=opcode.name,
252 value2=opcode.length))
253 if merged.nuses != opcode.nuses:
254 error("nuses should be same for merged section: "
255 "{value1}({name1}) != "
256 "{value2}({name2})".format(name1=merged.name,
257 value1=merged.nuses,
258 name2=opcode.name,
259 value2=opcode.nuses))
260 if merged.ndefs != opcode.ndefs:
261 error("ndefs should be same for merged section: "
262 "{value1}({name1}) != "
263 "{value2}({name2})".format(name1=merged.name,
264 value1=merged.ndefs,
265 name2=opcode.name,
266 value2=opcode.ndefs))
267 merged.group.append(opcode)
268 if opcode.name < merged.name:
269 merged.sort_key = opcode.name
271 opcode = OpcodeInfo()
273 return index
275 def override(value, override_value):
276 if override_value != '':
277 return override_value
279 return value
281 def format_flags(flags):
282 if flags == '':
283 return ''
285 return ' ({flags})'.format(flags=flags)
287 def print_opcode(opcode):
288 names_template = '{name} [-{nuses}, +{ndefs}]{flags}'
289 names = map(lambda code: names_template.format(name=escape(code.name),
290 nuses=override(code.nuses,
291 opcode.nuses_override),
292 ndefs=override(code.ndefs,
293 opcode.ndefs_override),
294 flags=format_flags(code.flags)),
295 sorted([opcode] + opcode.group,
296 key=lambda opcode: opcode.name))
297 if len(opcode.group) == 0:
298 values = ['{value} (0x{value:02x})'.format(value=opcode.value)]
299 else:
300 values_template = '{name}: {value} (0x{value:02x})'
301 values = map(lambda code: values_template.format(name=escape(code.name),
302 value=code.value),
303 sorted([opcode] + opcode.group,
304 key=lambda opcode: opcode.name))
306 print("""<dt>{names}</dt>
307 <dd>
308 <table class="standard-table">
309 <tr><th>Value</th><td><code>{values}</code></td></tr>
310 <tr><th>Operands</th><td><code>{operands}</code></td></tr>
311 <tr><th>Length</th><td><code>{length}</code></td></tr>
312 <tr><th>Stack Uses</th><td><code>{stack_uses}</code></td></tr>
313 <tr><th>Stack Defs</th><td><code>{stack_defs}</code></td></tr>
314 </table>
316 {desc}
317 </dd>
318 """.format(names='<br>'.join(names),
319 values='<br>'.join(values),
320 operands=escape(opcode.operands),
321 length=escape(override(opcode.length,
322 opcode.length_override)),
323 stack_uses=escape(opcode.stack_uses),
324 stack_defs=escape(opcode.stack_defs),
325 desc=opcode.desc)) # desc is already escaped
327 def make_element_id(name):
328 return name.replace(' ', '-')
330 def print_doc(version, index):
331 print("""<h2 id="Bytecode_Listing">Bytecode Listing</h2>
333 <p>This document is automatically generated from
334 <a href="{source_base}/js/src/vm/Opcodes.h">Opcodes.h</a> and
335 <a href="{source_base}/js/src/vm/Xdr.h">Xdr.h</a> by
336 <a href="{source_base}/js/src/vm/make_opcode_doc.py">make_opcode_doc.py</a>.</p>
338 <p>Bytecode version: <code>{version}</code>
339 (<code>0x{actual_version:08x}</code>).</p>
340 """.format(source_base=SOURCE_BASE,
341 version=version,
342 actual_version=0xb973c0de - version))
344 for (category_name, types) in index:
345 print('<h3 id="{id}">{name}</h3>'.format(name=category_name,
346 id=make_element_id(category_name)))
347 for (type_name, opcodes) in types:
348 if type_name:
349 print('<h4 id="{id}">{name}</h4>'.format(name=type_name,
350 id=make_element_id(type_name)))
351 print('<dl>')
352 for opcode in sorted(opcodes,
353 key=lambda opcode: opcode.sort_key):
354 print_opcode(opcode)
355 print('</dl>')
357 if __name__ == '__main__':
358 if len(sys.argv) < 2:
359 print("Usage: make_opcode_doc.py PATH_TO_MOZILLA_CENTRAL",
360 file=sys.stderr)
361 sys.exit(1)
362 dir = sys.argv[1]
363 version = get_xdr_version(dir)
364 index = get_opcodes(dir)
365 print_doc(version, index)