js/src/vm/make_opcode_doc.py

Wed, 31 Dec 2014 07:53:36 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:53:36 +0100
branch
TOR_BUG_3246
changeset 5
4ab42b5ab56c
permissions
-rw-r--r--

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)

mercurial