The base grammar we start out for a Python version even with the
        subclassing is, well, is pretty base.  And we want it that way: lean and
        mean so that parsing will go faster.

        Here, we add additional grammar rules based on specific instructions
        that are in the instruction/token stream. In classes that
        inherit from from here and other versions, grammar rules may
        also be removed.

        For example if we see a pretty rare JUMP_IF_NOT_DEBUG
        instruction we'll add the grammar for that.

        More importantly, here we add grammar rules for instructions
        that may access a variable number of stack items. CALL_FUNCTION,
        BUILD_LIST and so on are like this.

        Without custom rules, there can be an super-exponential number of
        derivations. See the deparsing paper for an elaboration of
        this.

def customize_grammar_rules(self, tokens, customize):
        """The base grammar we start out for a Python version even with the
        subclassing is, well, is pretty base.  And we want it that way: lean and
        mean so that parsing will go faster.

        Here, we add additional grammar rules based on specific instructions
        that are in the instruction/token stream. In classes that
        inherit from from here and other versions, grammar rules may
        also be removed.

        For example if we see a pretty rare JUMP_IF_NOT_DEBUG
        instruction we'll add the grammar for that.

        More importantly, here we add grammar rules for instructions
        that may access a variable number of stack items. CALL_FUNCTION,
        BUILD_LIST and so on are like this.

        Without custom rules, there can be an super-exponential number of
        derivations. See the deparsing paper for an elaboration of
        this.
        """

        if 'PyPy' in customize:
            # PyPy-specific customizations
            self.addRule("""
                        stmt ::= assign3_pypy
                        stmt ::= assign2_pypy
                        assign3_pypy ::= expr expr expr store store store
                        assign2_pypy ::= expr expr store store
                        list_comp    ::= expr  BUILD_LIST_FROM_ARG for_iter store list_iter
                                         JUMP_BACK
                        """, nop_func)

        # For a rough break out on the first word. This may
        # include instructions that don't need customization,
        # but we'll do a finer check after the rough breakout.
        customize_instruction_basenames = frozenset(
            ('BUILD',     'CALL',       'CONTINUE',  'DELETE',
             'DUP',       'EXEC',       'GET',       'JUMP',
             'LOAD',      'LOOKUP',     'MAKE',      'SETUP',
             'RAISE',     'UNPACK'))

        # Opcode names in the custom_seen_ops set have rules that get added
        # unconditionally and the rules are constant. So they need to be done
        # only once and if we see the opcode a second we don't have to consider
        # adding more rules.
        #
        custom_seen_ops = set()

        for i, token in enumerate(tokens):
            opname = token.kind

            # Do a quick breakout before testing potentially
            # each of the dozen or so instruction in if elif.
            if (opname[:opname.find('_')] not in customize_instruction_basenames
                    or opname in custom_seen_ops):
                continue

            opname_base = opname[:opname.rfind('_')]

            # The order of opname listed is roughly sorted below
            if opname_base in ('BUILD_LIST', 'BUILD_SET', 'BUILD_TUPLE'):
                # We do this complicated test to speed up parsing of
                # pathelogically long literals, especially those over 1024.
                build_count = token.attr
                thousands = (build_count//1024)
                thirty32s = ((build_count//32) % 32)
                if thirty32s > 0:
                    rule = "expr32 ::=%s" % (' expr' * 32)
                    self.add_unique_rule(rule, opname_base, build_count, customize)
                if thousands > 0:
                    self.add_unique_rule("expr1024 ::=%s" % (' expr32' * 32),
                                         opname_base, build_count, customize)
                collection = opname_base[opname_base.find('_')+1:].lower()
                rule = (('%s ::= ' % collection) + 'expr1024 '*thousands +
                        'expr32 '*thirty32s + 'expr '*(build_count % 32) + opname)
                self.add_unique_rules([
                    "expr ::= %s" % collection,
                    rule], customize)
                continue
            elif opname_base == 'BUILD_MAP':
                if opname == 'BUILD_MAP_n':
                    # PyPy sometimes has no count. Sigh.
                    self.add_unique_rules([
                        'kvlist_n ::=  kvlist_n kv3',
                        'kvlist_n ::=',
                        'dict ::= BUILD_MAP_n kvlist_n',
                    ], customize)
                    if self.version >= 2.7:
                        self.add_unique_rule(
                            'dict_comp_func ::= BUILD_MAP_n LOAD_FAST FOR_ITER store '
                            'comp_iter JUMP_BACK RETURN_VALUE RETURN_LAST',
                            'dict_comp_func', 0, customize)

                else:
                    kvlist_n =  ' kv3' * token.attr
                    rule = "dict ::= %s%s" % (opname, kvlist_n)
                    self.addRule(rule, nop_func)
                continue
            elif opname_base == 'BUILD_SLICE':
                slice_num  = token.attr
                if slice_num == 2:
                     self.add_unique_rules([
                        'expr ::= build_slice2',
                        'build_slice2 ::= expr expr BUILD_SLICE_2'
                        ], customize)
                else:
                    assert slice_num == 3, ("BUILD_SLICE value must be 2 or 3; is %s" %
                                            slice_num)
                    self.add_unique_rules([
                        'expr ::= build_slice3',
                        'build_slice3 ::= expr expr expr BUILD_SLICE_3',
                        ], customize)
                continue
            elif opname_base in ('CALL_FUNCTION', 'CALL_FUNCTION_VAR',
                                 'CALL_FUNCTION_VAR_KW', 'CALL_FUNCTION_KW'):

                args_pos, args_kw = self.get_pos_kw(token)

                # number of apply equiv arguments:
                nak = ( len(opname_base)-len('CALL_FUNCTION') ) // 3
                rule = 'call ::= expr ' + 'expr '*args_pos + 'kwarg '*args_kw \
                       + 'expr ' * nak + opname
            elif opname_base == 'CALL_METHOD':
                # PyPy only - DRY with parse3

                args_pos, args_kw = self.get_pos_kw(token)

                # number of apply equiv arguments:
                nak = ( len(opname_base)-len('CALL_METHOD') ) // 3
                rule = 'call ::= expr ' + 'expr '*args_pos + 'kwarg '*args_kw \
                       + 'expr ' * nak + opname
            elif opname == 'CONTINUE_LOOP':
                self.addRule('continue ::= CONTINUE_LOOP', nop_func)
                custom_seen_ops.add(opname)
                continue
            elif opname == 'DELETE_ATTR':
                self.addRule('del_stmt ::= expr DELETE_ATTR', nop_func)
                custom_seen_ops.add(opname)
                continue
            elif opname.startswith('DELETE_SLICE'):
                self.addRule("""
                del_expr ::= expr
                del_stmt ::= del_expr DELETE_SLICE+0
                del_stmt ::= del_expr del_expr DELETE_SLICE+1
                del_stmt ::= del_expr del_expr DELETE_SLICE+2
                del_stmt ::= del_expr del_expr del_expr DELETE_SLICE+3
                """, nop_func)
                custom_seen_ops.add(opname)
                self.check_reduce['del_expr'] = 'AST'
                continue
            elif opname == 'DELETE_DEREF':
                self.addRule("""
                   stmt           ::= del_deref_stmt
                   del_deref_stmt ::= DELETE_DEREF
                   """, nop_func)
                custom_seen_ops.add(opname)
                continue
            elif opname == 'DELETE_SUBSCR':
                self.addRule("""
                    del_stmt ::= delete_subscr
                    delete_subscr ::= expr expr DELETE_SUBSCR
                   """, nop_func)
                self.check_reduce['delete_subscr'] = 'AST'
                custom_seen_ops.add(opname)
                continue
            elif opname == 'GET_ITER':
                self.addRule("""
                    expr      ::= get_iter
                    attribute ::= expr GET_ITER
                    """, nop_func)
                custom_seen_ops.add(opname)
                continue
            elif opname_base in ('DUP_TOPX', 'RAISE_VARARGS'):
                # FIXME: remove these conditions if they are not needed.
                # no longer need to add a rule
                continue
            elif opname == 'EXEC_STMT':
                self.addRule("""
                    stmt      ::= exec_stmt
                    exec_stmt ::= expr exprlist DUP_TOP EXEC_STMT
                    exec_stmt ::= expr exprlist EXEC_STMT
                    exprlist  ::= expr+
                    """, nop_func)
                continue
            elif opname == 'JUMP_IF_NOT_DEBUG':
                self.addRule("""
                    jmp_true_false ::= POP_JUMP_IF_TRUE
                    jmp_true_false ::= POP_JUMP_IF_FALSE
                    stmt ::= assert_pypy
                    stmt ::= assert2_pypy
                    assert_pypy  ::= JUMP_IF_NOT_DEBUG assert_expr jmp_true_false
                                     LOAD_ASSERT RAISE_VARARGS_1 COME_FROM
                    assert2_pypy ::= JUMP_IF_NOT_DEBUG assert_expr jmp_true_false
                                     LOAD_ASSERT expr CALL_FUNCTION_1
                                     RAISE_VARARGS_1 COME_FROM
                     """, nop_func)
                continue
            elif opname == 'LOAD_ATTR':
                self.addRule("""
                  expr      ::= attribute
                  attribute ::= expr LOAD_ATTR
                  """, nop_func)
                custom_seen_ops.add(opname)
                continue
            elif opname == 'LOAD_LISTCOMP':
                self.addRule("expr ::= listcomp", nop_func)
                custom_seen_ops.add(opname)
                continue
            elif opname == 'LOAD_SETCOMP':
                self.add_unique_rules([
                    "expr ::= set_comp",
                    "set_comp ::= LOAD_SETCOMP MAKE_FUNCTION_0 expr GET_ITER CALL_FUNCTION_1"
                    ], customize)
                custom_seen_ops.add(opname)
                continue
            elif opname == 'LOOKUP_METHOD':
                # A PyPy speciality - DRY with parse3
                self.addRule("""
                             expr      ::= attribute
                             attribute ::= expr LOOKUP_METHOD
                             """,
                             nop_func)
                custom_seen_ops.add(opname)
                continue
            elif opname_base == 'MAKE_FUNCTION':
                if i > 0 and tokens[i-1] == 'LOAD_LAMBDA':
                    self.addRule('mklambda ::= %s LOAD_LAMBDA %s' %
                                 ('pos_arg ' * token.attr, opname), nop_func)
                rule = 'mkfunc ::= %s LOAD_CONST %s' % ('expr ' * token.attr, opname)
            elif opname_base == 'MAKE_CLOSURE':
                # FIXME: use add_unique_rules to tidy this up.
                if i > 0 and tokens[i-1] == 'LOAD_LAMBDA':
                    self.addRule('mklambda ::= %s load_closure LOAD_LAMBDA %s' %
                                 ('expr ' * token.attr, opname),  nop_func)
                if i > 0:
                    prev_tok = tokens[i-1]
                    if prev_tok == 'LOAD_GENEXPR':
                        self.add_unique_rules([
                            ('generator_exp ::= %s load_closure LOAD_GENEXPR %s expr'
                                 ' GET_ITER CALL_FUNCTION_1' %
                            ('expr ' * token.attr, opname))], customize)
                        pass
                self.add_unique_rules([
                    ('mkfunc ::= %s load_closure LOAD_CONST %s' %
                     ('expr ' * token.attr, opname))], customize)

                if self.version >= 2.7:
                    if i > 0:
                        prev_tok = tokens[i-1]
                        if prev_tok == 'LOAD_DICTCOMP':
                            self.add_unique_rules([
                                ('dict_comp ::= %s load_closure LOAD_DICTCOMP %s expr'
                                 ' GET_ITER CALL_FUNCTION_1' %
                                ('expr ' * token.attr, opname))], customize)
                        elif prev_tok == 'LOAD_SETCOMP':
                            self.add_unique_rules([
                                "expr ::= set_comp",
                                ('set_comp ::= %s load_closure LOAD_SETCOMP %s expr'
                                ' GET_ITER CALL_FUNCTION_1' %
                                ('expr ' * token.attr, opname))
                                ], customize)
                        pass
                    pass
                continue
            elif opname == 'SETUP_EXCEPT':
                if 'PyPy' in customize:
                    self.add_unique_rules([
                        "stmt ::= try_except_pypy",
                        "try_except_pypy ::= SETUP_EXCEPT suite_stmts_opt except_handler_pypy",
                        "except_handler_pypy ::= COME_FROM except_stmts END_FINALLY COME_FROM"
                        ], customize)
                custom_seen_ops.add(opname)
                continue
            elif opname == 'SETUP_FINALLY':
                if 'PyPy' in customize:
                    self.addRule("""
                        stmt ::= tryfinallystmt_pypy
                        tryfinallystmt_pypy ::= SETUP_FINALLY suite_stmts_opt COME_FROM_FINALLY
                                                suite_stmts_opt END_FINALLY""", nop_func)

                custom_seen_ops.add(opname)
                continue
            elif opname_base in ('UNPACK_TUPLE', 'UNPACK_SEQUENCE'):
                custom_seen_ops.add(opname)
                rule = 'unpack ::= ' + opname + ' store' * token.attr
            elif opname_base == 'UNPACK_LIST':
                custom_seen_ops.add(opname)
                rule = 'unpack_list ::= ' + opname + ' store' * token.attr
            else:
                continue
            self.addRule(rule, nop_func)
            pass

        self.check_reduce['raise_stmt1'] = 'tokens'
        self.check_reduce['aug_assign2'] = 'AST'
        self.check_reduce['or'] = 'AST'
        # self.check_reduce['_stmts'] = 'AST'

        # Dead code testing...
        # self.check_reduce['while1elsestmt'] = 'tokens'
        return