# (C) Copyright 2016-2017, 2019-2020 by Rocky Bernstein
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
"""
CPython 3.6 bytecode opcodes

This is a like Python 3.6's opcode.py with some classification
of stack usage.
"""

from xdis.opcodes.base import(
    def_op, finalize_opcodes,
    init_opdata, jrel_op,
    nargs_op, rm_op, store_op, varargs_op,
    update_pj3
    )

import xdis.opcodes.opcode_35 as opcode_35

# When we use EXTENDED_ARG, by how much do we
# shift (or what power of two do we multiply) the operand value?
# Note: this changes in Python 3.6
EXTENDED_ARG_SHIFT = 8

version = 3.6
python_implementation = "CPython"

l = locals()

init_opdata(l, opcode_35, version)

# These are removed since Python 3.6
rm_op(l, 'MAKE_CLOSURE',         134)
rm_op(l, 'CALL_FUNCTION_VAR',    140)
rm_op(l, 'CALL_FUNCTION_VAR_KW', 142)

### Opcodes that have changed drastically ####


# BUILD_MAP_UNPACK_WITH_CALL oparg

# oparg is the number of unpacked mappings which no longer limited by
# 255. As in BUILD_MAP_UNPACK.The location of the function is `oparg +
# 2`.


# CALL_FUNCTION oparg

# oparg is the number of positional arguments on the stack which no
# longer limited by 255.


# CALL_FUNCTION_KW oparg

# This is a different opcode from before, with the name of a similar
# opcode as before. It s like CALL_FUNCTION but values of keyword
# arguments are pushed on the stack after values of positional
# arguments, and then a constant tuple of keyword names is pushed on
# the top of the stack.  *oparg* is the sum of numbers of positional
# and keyword arguments.  The number of keyword arguments is
# determined by the length of the tuple of keyword names.

# CALL_FUNCTION_EX** takes 2 to 3 arguments on the stack: the
# function, the tuple of positional arguments, and optionally the dict
# of keyword arguments if bit 0 of *oparg* is 1.


# MAKE_FUNCTION oparg

# This is a different opcode from before.

# The tuple of default values for positional-or-keyword parameters,
# the dict of default values for keyword-only parameters, the dict of
# annotations and the closure are pushed on the stack if corresponding
# bit (0-3) is set. They are followed by the code object and the
# qualified name of the function.


# These are new since Python 3.6
#          OP NAME                OPCODE POP PUSH
#---------------------------------------------------
store_op(l,   'STORE_ANNOTATION', 127, 1, 0, is_type="name") # Stores TOS index in name list;
jrel_op(l,    'SETUP_ASYNC_WITH', 154, 2, 8)  # pops __aenter__ and __aexit__; pushed results on stack
def_op(l,     'FORMAT_VALUE',     155, 1, 1)
varargs_op(l, 'BUILD_CONST_KEY_MAP', 156, -2, 1) # TOS is count of kwargs
nargs_op(l,   'CALL_FUNCTION_EX', 142, -2, 1)
def_op(l,     'SETUP_ANNOTATIONS', 85, 1, 1)
varargs_op(l,  'BUILD_STRING',     157, -2, 2)
varargs_op(l,  'BUILD_TUPLE_UNPACK_WITH_CALL', 158)

MAKE_FUNCTION_FLAGS = tuple("default keyword-only annotation closure".split())

def format_MAKE_FUNCTION_arg(flags):
    if flags == 0:
        return "Neither defaults, keyword-only args, annotations, nor closures"
    pattr = ''
    for flag in MAKE_FUNCTION_FLAGS:
        bit = flags & 1
        if bit:
            if pattr:
                pattr += ", " + flag
            else:
                pattr = flag
                pass
            pass
        flags >>= 1
    return pattr

def format_value_flags(flags):
    if (flags & 0x03) == 0x00:
        return ''
    elif (flags & 0x03) == 0x01:
        return '!s'
    elif (flags & 0x03) == 0x02:
        return '!r'
    elif (flags & 0x03) == 0x03:
        return '!a'
    elif (flags & 0x04) == 0x04:
        # pop fmt_spec from the stack and use it, else use an
        # empty fmt_spec.
        return ''

def format_extended_arg36(arg):
    return str(arg * (1 << 8))

def format_CALL_FUNCTION(argc):
    """argc indicates the number of positional arguments"""
    if argc == 1:
        plural = ""
    else:
        plural = "s"
    return ("%d positional argument%s" % (argc, plural))

def format_CALL_FUNCTION_EX(flags):
    str = ""
    if flags & 0x01:
        str = "keyword and positional arguments"
    else:
        str = "positional arguments only"
    return str

def format_CALL_FUNCTION_KW(argc):
    return "%d total positional and keyword args" % argc

# The meaning of argc changes from 3.5 where this was introduced.
def format_BUILD_MAP_UNPACK_WITH_CALL(count):
    """The lowest byte of oparg is the count of mappings, the relative
    position of the corresponding callable f is encoded in the second byte
    of oparg."""
    return ("%d mappings" % count)

opcode_arg_fmt = {
    "BUILD_MAP_UNPACK_WITH_CALL": format_BUILD_MAP_UNPACK_WITH_CALL,
    "CALL_FUNCTION": format_CALL_FUNCTION,
    "CALL_FUNCTION_KW": format_CALL_FUNCTION_KW,
    "CALL_FUNCTION_EX": format_CALL_FUNCTION_EX,
    "MAKE_FUNCTION": format_MAKE_FUNCTION_arg,
    "FORMAT_VALUE": format_value_flags,
    "EXTENDED_ARG": format_extended_arg36
}

update_pj3(globals(), l)

finalize_opcodes(l)
