Bug 1343417 - Verify bytecode documentation in js/src/vm/Opcodes.h in make check. r=nbp
authorTooru Fujisawa <arai_a@mac.com>
Mon, 03 Apr 2017 10:14:38 +0900
changeset 350901 c829d4bc7c0275a5df379da7855b1a9199991226
parent 350900 90d953cd71fbabda2d1e442d8fba2dc7f60d4905
child 350902 967edd49939b0f42cfddbe488ca9b7044c2b4f53
push id31594
push usercbook@mozilla.com
push dateMon, 03 Apr 2017 10:14:16 +0000
treeherdermozilla-central@aaa0cd3bd620 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnbp
bugs1343417
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1343417 - Verify bytecode documentation in js/src/vm/Opcodes.h in make check. r=nbp
config/check_js_opcode.py
js/src/Makefile.in
js/src/vm/Opcodes.h
js/src/vm/make_opcode_doc.py
js/src/vm/opcode.py
new file mode 100644
--- /dev/null
+++ b/config/check_js_opcode.py
@@ -0,0 +1,43 @@
+# vim: set ts=8 sts=4 et sw=4 tw=99:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#----------------------------------------------------------------------------
+# This script checks bytecode documentation in js/src/vm/Opcodes.h
+#----------------------------------------------------------------------------
+
+from __future__ import print_function
+
+import os
+import sys
+
+scriptname = os.path.basename(__file__);
+topsrcdir = os.path.dirname(os.path.dirname(__file__))
+
+def log_pass(text):
+    print('TEST-PASS | {} | {}'.format(scriptname, text))
+
+def log_fail(text):
+    print('TEST-UNEXPECTED-FAIL | {} | {}'.format(scriptname, text))
+
+def check_opcode():
+    sys.path.insert(0, os.path.join(topsrcdir, 'js', 'src', 'vm'))
+    import opcode
+
+    try:
+        opcode.get_opcodes(topsrcdir)
+    except Exception as e:
+        log_fail(e.args[0])
+
+    log_pass('ok')
+    return True
+
+def main():
+    if not check_opcode():
+        sys.exit(1)
+
+    sys.exit(0)
+
+if __name__ == '__main__':
+    main()
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -88,24 +88,27 @@ check-style::
 	(cd $(srcdir) && $(PYTHON) $(topsrcdir)/config/check_spidermonkey_style.py);
 
 check-masm::
 	(cd $(srcdir) && $(PYTHON) $(topsrcdir)/config/check_macroassembler_style.py);
 
 check-js-msg::
 	(cd $(topsrcdir) && $(PYTHON) $(topsrcdir)/config/check_js_msg_encoding.py);
 
+check-opcode::
+	(cd $(topsrcdir) && $(PYTHON) $(topsrcdir)/config/check_js_opcode.py);
+
 check-jit-test::
 	$(JITTEST_SANITIZER_ENV) $(wildcard $(RUN_TEST_PROGRAM)) $(PYTHON) -u $(srcdir)/jit-test/jit_test.py \
 	        --no-slow --no-progress --format=automation --jitflags=all \
 			$(JITTEST_VALGRIND_FLAG) \
 			$(JITTEST_EXTRA_ARGS) \
 	        $(DIST)/bin/$(JS_SHELL_NAME)$(BIN_SUFFIX) $(JITTEST_TEST_ARGS)
 
-check:: check-style check-masm check-js-msg
+check:: check-style check-masm check-js-msg check-opcode
 
 check-jstests:
 	$(wildcard $(RUN_TEST_PROGRAM)) $(PYTHON) -u $(srcdir)/tests/jstests.py \
 		--no-progress --format=automation --timeout 300 \
 		$(JSTESTS_EXTRA_ARGS) \
 		$(DIST)/bin/$(JS_SHELL_NAME)$(BIN_SUFFIX)
 
 # FIXME:
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -426,17 +426,17 @@ 1234567890123456789012345678901234567890
      *          v[n], v[n-1], ..., v[1], v[0], v[n]
      */ \
     macro(JSOP_DUPAT,     44, "dupat",      NULL,         4,  0,  1,  JOF_UINT24) \
     \
     /*
      * Push a well-known symbol onto the operand stack.
      *   Category: Literals
      *   Type: Constants
-     *   Operands: uint8_t n, the JS::SymbolCode of the symbol to use
+     *   Operands: uint8_t symbol (the JS::SymbolCode of the symbol to use)
      *   Stack: => symbol
      */ \
     macro(JSOP_SYMBOL,    45, "symbol",     NULL,         2,  0,  1,  JOF_UINT8) \
     \
     /*
      * Pops the top of stack value and attempts to delete the given property
      * from it. Pushes 'true' onto success, else throws a TypeError per strict
      * mode property-deletion requirements.
@@ -581,17 +581,17 @@ 1234567890123456789012345678901234567890
      *   Stack: => val
      */ \
     macro(JSOP_DOUBLE,    60, "double",     NULL,         5,  0,  1, JOF_DOUBLE) \
     /*
      * Pushes string constant onto the stack.
      *   Category: Literals
      *   Type: Constants
      *   Operands: uint32_t atomIndex
-     *   Stack: => string
+     *   Stack: => atom
      */ \
     macro(JSOP_STRING,    61, "string",     NULL,         5,  0,  1, JOF_ATOM) \
     /*
      * Pushes '0' onto the stack.
      *   Category: Literals
      *   Type: Constants
      *   Operands:
      *   Stack: => 0
--- a/js/src/vm/make_opcode_doc.py
+++ b/js/src/vm/make_opcode_doc.py
@@ -8,302 +8,38 @@
     Output is written to stdout and should be pasted into the following
     MDN page:
     https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
 """
 
 from __future__ import print_function
 import re
 import sys
+
+import os
+sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
+import opcode
+
 from xml.sax.saxutils import escape
 
 SOURCE_BASE = 'http://dxr.mozilla.org/mozilla-central/source'
 
-def error(message):
-    print("Error: {message}".format(message=message), file=sys.stderr)
-    sys.exit(1)
-
-quoted_pat = re.compile(r"([^A-Za-z0-9]|^)'([^']+)'")
-js_pat = re.compile(r"([^A-Za-z0-9]|^)(JS[A-Z0-9_\*]+)")
-def codify(text):
-    text = re.sub(quoted_pat, '\\1<code>\\2</code>', text)
-    text = re.sub(js_pat, '\\1<code>\\2</code>', text)
-
-    return text
-
-space_star_space_pat = re.compile('^\s*\* ?', re.M)
-def get_comment_body(comment):
-    return re.sub(space_star_space_pat, '', comment).split('\n')
-
-def get_stack_count(stack):
-    if stack == "":
-        return 0
-    if '...' in stack:
-        return -1
-    return len(stack.split(','))
-
-def parse_index(comment):
-    index = []
-    current_types = None
-    category_name = ''
-    category_pat = re.compile('\[([^\]]+)\]')
-    for line in get_comment_body(comment):
-        m = category_pat.search(line)
-        if m:
-            category_name = m.group(1)
-            if category_name == 'Index':
-                continue
-            current_types = []
-            index.append((category_name, current_types))
-        else:
-            type_name = line.strip()
-            if type_name and current_types is not None:
-                current_types.append((type_name, []))
-
-    return index
-
-class OpcodeInfo:
-    def __init__(self):
-        self.name = ''
-        self.value = ''
-        self.length = ''
-        self.length_override = ''
-        self.nuses = ''
-        self.nuses_override = ''
-        self.ndefs = ''
-        self.ndefs_override = ''
-        self.flags = ''
-        self.operands = ''
-        self.stack_uses = ''
-        self.stack_defs = ''
-
-        self.desc = ''
-
-        self.category_name = ''
-        self.type_name = ''
-
-        self.group = []
-        self.sort_key = ''
-
-def find_by_name(list, name):
-    for (n, body) in list:
-        if n == name:
-            return body
-
-    return None
-
-def add_to_index(index, opcode):
-    types = find_by_name(index, opcode.category_name)
-    if types is None:
-        error("Category is not listed in index: "
-              "{name}".format(name=opcode.category_name))
-    opcodes = find_by_name(types, opcode.type_name)
-    if opcodes is None:
-        if opcode.type_name:
-            error("Type is not listed in {category}: "
-                  "{name}".format(category=opcode.category_name,
-                                  name=opcode.type_name))
-        types.append((opcode.type_name, [opcode]))
-        return
-
-    opcodes.append(opcode)
-
-def format_desc(descs):
-    current_type = ''
-    desc = ''
-    for (type, line) in descs:
-        if type != current_type:
-            if current_type:
-                desc += '</{name}>\n'.format(name=current_type)
-            current_type = type
-            if type:
-                desc += '<{name}>'.format(name=current_type)
-        if current_type:
-            desc += line + "\n"
-    if current_type:
-        desc += '</{name}>'.format(name=current_type)
-
-    return desc
-
-tag_pat = re.compile('^\s*[A-Za-z]+:\s*|\s*$')
-def get_tag_value(line):
-    return re.sub(tag_pat, '', line)
-
-def get_opcodes(dir):
-    iter_pat = re.compile(r"/\*(.*?)\*/"  # either a documentation comment...
-                          r"|"
-                          r"macro\("      # or a macro(...) call
-                                 r"([^,]+),\s*"     # op
-                                 r"([0-9]+),\s*"    # val
-                                 r"[^,]+,\s*"       # name
-                                 r"[^,]+,\s*"       # image
-                                 r"([0-9\-]+),\s*"  # length
-                                 r"([0-9\-]+),\s*"  # nuses
-                                 r"([0-9\-]+),\s*"  # ndefs
-                                 r"([^\)]+)"        # format
-                          r"\)", re.S)
-    stack_pat = re.compile('^(.*?)\s*=>\s*(.*?)$')
-
-    index = []
-
-    opcode = OpcodeInfo()
-    merged = opcode
-
-    with open('{dir}/js/src/vm/Opcodes.h'.format(dir=dir), 'r') as f:
-        data = f.read()
-
-    for m in re.finditer(iter_pat, data):
-        comment = m.group(1)
-        name = m.group(2)
-
-        if comment:
-            if '[Index]' in comment:
-                index = parse_index(comment)
-                continue
-
-            if 'Operands:' not in comment:
-                continue
-
-            state = 'desc'
-            stack = ''
-            descs = []
-
-            for line in get_comment_body(comment):
-                if line.startswith('  Category:'):
-                    state = 'category'
-                    opcode.category_name = get_tag_value(line)
-                elif line.startswith('  Type:'):
-                    state = 'type'
-                    opcode.type_name = get_tag_value(line)
-                elif line.startswith('  Operands:'):
-                    state = 'operands'
-                    opcode.operands = get_tag_value(line)
-                elif line.startswith('  Stack:'):
-                    state = 'stack'
-                    stack = get_tag_value(line)
-                elif line.startswith('  len:'):
-                    state = 'len'
-                    opcode.length_override = get_tag_value(line)
-                elif line.startswith('  nuses:'):
-                    state = 'nuses'
-                    opcode.nuses_override = get_tag_value(line)
-                elif line.startswith('  ndefs:'):
-                    state = 'ndefs'
-                    opcode.ndefs_override = get_tag_value(line)
-                elif state == 'desc':
-                    if line.startswith(' '):
-                        descs.append(('pre', escape(line[1:])))
-                    else:
-                        line = line.strip()
-                        if line == '':
-                            descs.append(('', line))
-                        else:
-                            descs.append(('p', codify(escape(line))))
-                elif line.startswith('  '):
-                    if state == 'operands':
-                        opcode.operands += line.strip()
-                    elif state == 'stack':
-                        stack += line.strip()
-                    elif state == 'len':
-                        opcode.length_override += line.strip()
-                    elif state == 'nuses':
-                        opcode.nuses_override += line.strip()
-                    elif state == 'ndefs':
-                        opcode.ndefs_override += line.strip()
-
-            opcode.desc = format_desc(descs)
-
-            m2 = stack_pat.search(stack)
-            if m2:
-                opcode.stack_uses = m2.group(1)
-                opcode.stack_defs = m2.group(2)
-
-            merged = opcode
-        elif name and not name.startswith('JSOP_UNUSED'):
-            opcode.name = name
-            opcode.value = int(m.group(3))
-            opcode.length = m.group(4)
-            opcode.nuses = m.group(5)
-            opcode.ndefs = m.group(6)
-
-            flags = []
-            for flag in m.group(7).split('|'):
-                if flag != 'JOF_BYTE':
-                    flags.append(flag.replace('JOF_', ''))
-            opcode.flags = ', '.join(flags)
-
-            if merged == opcode:
-                opcode.sort_key = opcode.name
-                if opcode.category_name == '':
-                    error("Category is not specified for "
-                          "{name}".format(name=opcode.name))
-                add_to_index(index, opcode)
-            else:
-                if merged.length != opcode.length:
-                    error("length should be same for merged section: "
-                          "{value1}({name1}) != "
-                          "{value2}({name2})".format(name1=merged.name,
-                                                     value1=merged.length,
-                                                     name2=opcode.name,
-                                                     value2=opcode.length))
-                if merged.nuses != opcode.nuses:
-                    error("nuses should be same for merged section: "
-                          "{value1}({name1}) != "
-                          "{value2}({name2})".format(name1=merged.name,
-                                                     value1=merged.nuses,
-                                                     name2=opcode.name,
-                                                     value2=opcode.nuses))
-                if merged.ndefs != opcode.ndefs:
-                    error("ndefs should be same for merged section: "
-                          "{value1}({name1}) != "
-                          "{value2}({name2})".format(name1=merged.name,
-                                                     value1=merged.ndefs,
-                                                     name2=opcode.name,
-                                                     value2=opcode.ndefs))
-                merged.group.append(opcode)
-                if opcode.name < merged.name:
-                    merged.sort_key = opcode.name
-
-            # Verify stack notation.
-            nuses = int(merged.nuses)
-            ndefs = int(merged.ndefs)
-
-            stack_nuses = get_stack_count(merged.stack_uses)
-            stack_ndefs = get_stack_count(merged.stack_defs)
-
-            if nuses != -1 and stack_nuses != -1 and nuses != stack_nuses:
-                error("nuses should match stack notation: {name}: "
-                      "{nuses} != {stack_nuses} "
-                      "(stack_nuses)".format(name=name,
-                                             nuses=nuses,
-                                             stack_nuses=stack_nuses,
-                                             stack_uses=merged.stack_uses))
-            if ndefs != -1 and stack_ndefs != -1 and ndefs != stack_ndefs:
-                error("ndefs should match stack notation: {name}: "
-                      "{ndefs} != {stack_ndefs} "
-                      "(stack_ndefs)".format(name=name,
-                                             ndefs=ndefs,
-                                             stack_ndefs=stack_ndefs,
-                                             stack_defs=merged.stack_defs))
-
-            opcode = OpcodeInfo()
-
-    return index
-
 def override(value, override_value):
     if override_value != '':
         return override_value
 
     return value
 
 def format_flags(flags):
-    if flags == '':
+    flags = filter(lambda x: x != 'JOF_BYTE', flags)
+    if len(flags) == 0:
         return ''
 
-    return ' ({flags})'.format(flags=flags)
+    flags = map(lambda x: x.replace('JOF_', ''), flags)
+    return ' ({flags})'.format(flags=', '.join(flags))
 
 def print_opcode(opcode):
     names_template = '{name} [-{nuses}, +{ndefs}]{flags}'
     opcodes = sorted([opcode] + opcode.group,
                      key=lambda opcode: opcode.name)
     names = map(lambda code: names_template.format(name=escape(code.name),
                                                    nuses=override(code.nuses,
                                                                   opcode.nuses_override),
@@ -389,10 +125,16 @@ def print_doc(index):
             print('</dl>')
 
 if __name__ == '__main__':
     if len(sys.argv) < 2:
         print("Usage: make_opcode_doc.py PATH_TO_MOZILLA_CENTRAL",
               file=sys.stderr)
         sys.exit(1)
     dir = sys.argv[1]
-    index = get_opcodes(dir)
+
+    try:
+        index, _ = opcode.get_opcodes(dir)
+    except Exception as e:
+        print("Error: {}".format(e.args[0]), file=sys.stderr)
+        sys.exit(1)
+
     print_doc(index)
new file mode 100644
--- /dev/null
+++ b/js/src/vm/opcode.py
@@ -0,0 +1,357 @@
+#!/usr/bin/python -B
+
+from __future__ import print_function
+import re
+import sys
+from xml.sax.saxutils import escape
+
+quoted_pat = re.compile(r"([^A-Za-z0-9]|^)'([^']+)'")
+js_pat = re.compile(r"([^A-Za-z0-9]|^)(JS[A-Z0-9_\*]+)")
+def codify(text):
+    text = re.sub(quoted_pat, '\\1<code>\\2</code>', text)
+    text = re.sub(js_pat, '\\1<code>\\2</code>', text)
+
+    return text
+
+space_star_space_pat = re.compile('^\s*\* ?', re.M)
+def get_comment_body(comment):
+    return re.sub(space_star_space_pat, '', comment).split('\n')
+
+quote_pat = re.compile('"([^"]+)"')
+str_pat = re.compile('js_([^_]+)_str')
+def parse_name(s):
+    m = quote_pat.search(s)
+    if m:
+        return m.group(1)
+    m = str_pat.search(s)
+    if m:
+        return m.group(1)
+    return s
+
+csv_pat = re.compile(', *')
+def parse_csv(s):
+    a = csv_pat.split(s)
+    if len(a) == 1 and a[0] == '':
+        return []
+    return a
+
+def get_stack_count(stack):
+    if stack == '':
+        return 0
+    if '...' in stack:
+        return -1
+    return len(stack.split(','))
+
+def parse_index(comment):
+    index = []
+    current_types = None
+    category_name = ''
+    category_pat = re.compile('\[([^\]]+)\]')
+    for line in get_comment_body(comment):
+        m = category_pat.search(line)
+        if m:
+            category_name = m.group(1)
+            if category_name == 'Index':
+                continue
+            current_types = []
+            index.append((category_name, current_types))
+        else:
+            type_name = line.strip()
+            if type_name and current_types is not None:
+                current_types.append((type_name, []))
+
+    return index
+
+# Holds the information stored in the comment with the following format:
+#   /*
+#    * {desc}
+#    *   Category: {category_name}
+#    *   Type: {type_name}
+#    *   Operands: {operands}
+#    *   Stack: {stack_uses} => {stack_defs}
+#    *   length: {length_override}
+#    *   nuses: {nuses_override}
+#    *   ndefs: {ndefs_override}
+#    */
+class CommentInfo:
+    def __init__(self):
+        self.desc = ''
+        self.category_name = ''
+        self.type_name = ''
+        self.operands = ''
+        self.stack_uses = ''
+        self.stack_defs = ''
+        self.length_override = ''
+        self.nuses_override = ''
+        self.ndefs_override = ''
+
+# Holds the information stored in the macro with the following format:
+#   macro({name}, {value}, {display_name}, {image}, {length}, {nuses}, {ndefs},
+#         {flags})
+# and the information from CommentInfo.
+class OpcodeInfo:
+    def __init__(self, comment_info):
+        self.name = ''
+        self.value = ''
+        self.display_name = ''
+        self.image = ''
+        self.length = ''
+        self.nuses = ''
+        self.ndefs = ''
+        self.flags = ''
+
+        self.operands_array = []
+        self.stack_uses_array = []
+        self.stack_defs_array = []
+
+        self.desc = comment_info.desc
+        self.category_name = comment_info.category_name
+        self.type_name = comment_info.type_name
+        self.operands = comment_info.operands
+        self.operands_array = comment_info.operands_array
+        self.stack_uses = comment_info.stack_uses
+        self.stack_uses_array = comment_info.stack_uses_array
+        self.stack_defs = comment_info.stack_defs
+        self.stack_defs_array = comment_info.stack_defs_array
+        self.length_override = comment_info.length_override
+        self.nuses_override = comment_info.nuses_override
+        self.ndefs_override = comment_info.ndefs_override
+
+        # List of OpcodeInfo that corresponds to macros after this.
+        #   /*
+        #    * comment
+        #    */
+        #   macro(JSOP_SUB, ...)
+        #   macro(JSOP_MUL, ...)
+        #   macro(JSOP_DIV, ...)
+        self.group = []
+
+        self.sort_key = ''
+
+def find_by_name(list, name):
+    for (n, body) in list:
+        if n == name:
+            return body
+
+    return None
+
+def add_to_index(index, opcode):
+    types = find_by_name(index, opcode.category_name)
+    if types is None:
+        raise Exception('Category is not listed in index: '
+                        '{name}'.format(name=opcode.category_name))
+    opcodes = find_by_name(types, opcode.type_name)
+    if opcodes is None:
+        if opcode.type_name:
+            raise Exception('Type is not listed in {category}: '
+                            '{name}'.format(category=opcode.category_name,
+                                            name=opcode.type_name))
+        types.append((opcode.type_name, [opcode]))
+        return
+
+    opcodes.append(opcode)
+
+def format_desc(descs):
+    current_type = ''
+    desc = ''
+    for (type, line) in descs:
+        if type != current_type:
+            if current_type:
+                desc += '</{name}>\n'.format(name=current_type)
+            current_type = type
+            if type:
+                desc += '<{name}>'.format(name=current_type)
+        if current_type:
+            desc += line + '\n'
+    if current_type:
+        desc += '</{name}>'.format(name=current_type)
+
+    return desc
+
+tag_pat = re.compile('^\s*[A-Za-z]+:\s*|\s*$')
+def get_tag_value(line):
+    return re.sub(tag_pat, '', line)
+
+def get_opcodes(dir):
+    iter_pat = re.compile(r"/\*(.*?)\*/"  # either a documentation comment...
+                          r"|"
+                          r"macro\("      # or a macro(...) call
+                                 r"(?P<name>[^,]+),\s*"
+                                 r"(?P<value>[0-9]+),\s*"
+                                 r"(?P<display_name>[^,]+,)\s*"
+                                 r"(?P<image>[^,]+),\s*"
+                                 r"(?P<length>[0-9\-]+),\s*"
+                                 r"(?P<nuses>[0-9\-]+),\s*"
+                                 r"(?P<ndefs>[0-9\-]+),\s*"
+                                 r"(?P<flags>[^\)]+)"
+                          r"\)", re.S)
+    stack_pat = re.compile(r"^(?P<uses>.*?)"
+                           r"\s*=>\s*"
+                           r"(?P<defs>.*?)$")
+
+    opcodes = dict()
+    index = []
+
+    with open('{dir}/js/src/vm/Opcodes.h'.format(dir=dir), 'r') as f:
+        data = f.read()
+
+    comment_info = None
+    opcode = None
+
+    # The first opcode after the comment.
+    group_head = None
+
+    for m in re.finditer(iter_pat, data):
+        comment = m.group(1)
+        name = m.group('name')
+
+        if comment:
+            if '[Index]' in comment:
+                index = parse_index(comment)
+                continue
+
+            if 'Operands:' not in comment:
+                continue
+
+            group_head = None
+
+            comment_info = CommentInfo()
+
+            state = 'desc'
+            stack = ''
+            descs = []
+
+            for line in get_comment_body(comment):
+                if line.startswith('  Category:'):
+                    state = 'category'
+                    comment_info.category_name = get_tag_value(line)
+                elif line.startswith('  Type:'):
+                    state = 'type'
+                    comment_info.type_name = get_tag_value(line)
+                elif line.startswith('  Operands:'):
+                    state = 'operands'
+                    comment_info.operands = get_tag_value(line)
+                elif line.startswith('  Stack:'):
+                    state = 'stack'
+                    stack = get_tag_value(line)
+                elif line.startswith('  len:'):
+                    state = 'len'
+                    comment_info.length_override = get_tag_value(line)
+                elif line.startswith('  nuses:'):
+                    state = 'nuses'
+                    comment_info.nuses_override = get_tag_value(line)
+                elif line.startswith('  ndefs:'):
+                    state = 'ndefs'
+                    comment_info.ndefs_override = get_tag_value(line)
+                elif state == 'desc':
+                    if line.startswith(' '):
+                        descs.append(('pre', escape(line[1:])))
+                    else:
+                        line = line.strip()
+                        if line == '':
+                            descs.append(('', line))
+                        else:
+                            descs.append(('p', codify(escape(line))))
+                elif line.startswith('  '):
+                    if state == 'operands':
+                        comment_info.operands += line.strip()
+                    elif state == 'stack':
+                        stack += line.strip()
+                    elif state == 'len':
+                        comment_info.length_override += line.strip()
+                    elif state == 'nuses':
+                        comment_info.nuses_override += line.strip()
+                    elif state == 'ndefs':
+                        comment_info.ndefs_override += line.strip()
+
+            comment_info.desc = format_desc(descs)
+
+            comment_info.operands_array = parse_csv(comment_info.operands)
+            comment_info.stack_uses_array = parse_csv(comment_info.stack_uses)
+            comment_info.stack_defs_array = parse_csv(comment_info.stack_defs)
+
+            m2 = stack_pat.search(stack)
+            if m2:
+                comment_info.stack_uses = m2.group('uses')
+                comment_info.stack_defs = m2.group('defs')
+        elif name and not name.startswith('JSOP_UNUSED'):
+            opcode = OpcodeInfo(comment_info)
+
+            opcode.name = name
+            opcode.value = int(m.group('value'))
+            opcode.display_name = parse_name(m.group('display_name'))
+            opcode.image = parse_name(m.group('image'))
+            opcode.length = m.group('length')
+            opcode.nuses = m.group('nuses')
+            opcode.ndefs = m.group('ndefs')
+            opcode.flags = m.group('flags').split('|')
+
+            if not group_head:
+                group_head = opcode
+
+                opcode.sort_key = opcode.name
+                if opcode.category_name == '':
+                    raise Exception('Category is not specified for '
+                                    '{name}'.format(name=opcode.name))
+                add_to_index(index, opcode)
+            else:
+                if group_head.length != opcode.length:
+                    raise Exception('length should be same for opcodes of the'
+                                    ' same group: '
+                                    '{value1}({name1}) != '
+                                    '{value2}({name2})'.format(
+                                        name1=group_head.name,
+                                        value1=group_head.length,
+                                        name2=opcode.name,
+                                        value2=opcode.length))
+                if group_head.nuses != opcode.nuses:
+                    raise Exception('nuses should be same for opcodes of the'
+                                    ' same group: '
+                                    '{value1}({name1}) != '
+                                    '{value2}({name2})'.format(
+                                        name1=group_head.name,
+                                        value1=group_head.nuses,
+                                        name2=opcode.name,
+                                        value2=opcode.nuses))
+                if group_head.ndefs != opcode.ndefs:
+                    raise Exception('ndefs should be same for opcodes of the'
+                                    ' same group: '
+                                    '{value1}({name1}) != '
+                                    '{value2}({name2})'.format(
+                                        name1=group_head.name,
+                                        value1=group_head.ndefs,
+                                        name2=opcode.name,
+                                        value2=opcode.ndefs))
+
+                group_head.group.append(opcode)
+
+                if opcode.name < group_head.name:
+                    group_head.sort_key = opcode.name
+
+            opcodes[name] = opcode
+
+            # Verify stack notation.
+            nuses = int(opcode.nuses)
+            ndefs = int(opcode.ndefs)
+
+            stack_nuses = get_stack_count(opcode.stack_uses)
+            stack_ndefs = get_stack_count(opcode.stack_defs)
+
+            if nuses != -1 and stack_nuses != -1 and nuses != stack_nuses:
+                raise Exception('nuses should match stack notation: {name}: '
+                                '{nuses} != {stack_nuses} '
+                                '(stack_nuses)'.format(
+                                    name=name,
+                                    nuses=nuses,
+                                    stack_nuses=stack_nuses,
+                                    stack_uses=opcode.stack_uses))
+            if ndefs != -1 and stack_ndefs != -1 and ndefs != stack_ndefs:
+                raise Exception('ndefs should match stack notation: {name}: '
+                                '{ndefs} != {stack_ndefs} '
+                                '(stack_ndefs)'.format(
+                                    name=name,
+                                    ndefs=ndefs,
+                                    stack_ndefs=stack_ndefs,
+                                    stack_defs=opcode.stack_defs))
+
+    return index, opcodes