File 313-compatibility.patch of Package python-Js2Py

From 0da30c83f149b15539ca3d246d5ebfb0afd7f140 Mon Sep 17 00:00:00 2001
From: a-j-albert <aalbert@cyberdude.com>
Date: Sun, 15 Dec 2024 22:03:31 -0800
Subject: [PATCH 1/2] My changes for 3.13

---
 js2py/utils/injector.py |   78 +++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 71 insertions(+), 7 deletions(-)

--- a/js2py/utils/injector.py
+++ b/js2py/utils/injector.py
@@ -15,6 +15,10 @@ LOAD_FAST = opcode.opmap['LOAD_FAST']
 LOAD_GLOBAL = opcode.opmap['LOAD_GLOBAL']
 LOAD_ATTR = opcode.opmap['LOAD_ATTR']
 STORE_FAST = opcode.opmap['STORE_FAST']
+LOAD_FAST_LOAD_FAST = opcode.opmap.get('LOAD_FAST_LOAD_FAST')
+STORE_FAST_LOAD_FAST = opcode.opmap.get('STORE_FAST_LOAD_FAST')
+STORE_FAST_STORE_FAST = opcode.opmap.get('STORE_FAST_STORE_FAST')
+EXTENDED_ARG = opcode.opmap.get('EXTENDED_ARG')
 
 
 _func_closure = "__closure__"
@@ -43,6 +47,40 @@ def fix_js_args(func):
         closure=get_function_closure(func))
     return result
 
+def get_list_of_var_indices_in_compound_opcodes(code_obj, non_arg_varnames, arg_count): #E.g. STORE_FAST_LOAD_FAST
+    indices = set()
+    for inst in instructions(code_obj):
+        extended = False
+        if inst.opcode == EXTENDED_ARG:
+            extended = True
+            continue
+        if not extended and inst.opcode in (LOAD_FAST_LOAD_FAST, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST):
+            first_index = (inst.arg >> 4 & 0xF) - arg_count
+            second_index = (inst.arg & 0xF) - arg_count
+            if first_index >= 0:
+                indices.add(first_index)
+            if second_index >= 0:
+                indices.add(second_index)
+        extended = False
+    return indices
+
+def rearrange_by_indices(strings, indices):
+    """
+    Rearranges the strings in the list so that the elements corresponding
+    to the indices are at the beginning of the list.
+
+    Args:
+        strings (list): The list of strings.
+        indices (list): A list of indices into the strings list.
+
+    Returns:
+        list: A new list with rearranged elements.
+    """
+    # Extract elements based on indices
+    prioritized = [strings[i] for i in indices if 0 <= i < len(strings)]
+    # Include elements not in indices
+    remaining = [strings[i] for i in range(len(strings)) if i not in indices]
+    return prioritized + remaining
 
 def append_arguments(code_obj, new_locals):
     co_varnames = code_obj.co_varnames  # Old locals
@@ -56,9 +94,9 @@ def append_arguments(code_obj, new_local
     # left in code_obj.co_names.
     not_removed = set(opcode.hasname) - set([LOAD_GLOBAL])
     saved_names = set()
-    for inst in instructions(code_obj):
-        if inst[0] in not_removed:
-            saved_names.add(co_names[inst[1]])
+    # for inst in instructions(code_obj):
+    #     if inst.opcode in not_removed:
+    #         saved_names.add(co_names[inst.oparg])
 
     # Build co_names for the new code object. This should consist of
     # globals that were only accessed via LOAD_GLOBAL
@@ -70,18 +108,21 @@ def append_arguments(code_obj, new_local
     name_translations = dict(
         (co_names.index(name), i) for i, name in enumerate(names))
 
+    non_arg_varnames = co_varnames[co_argcount:] # All varibles after the arguments
+    early_indices = get_list_of_var_indices_in_compound_opcodes(code_obj, non_arg_varnames, co_argcount)
+    rearranged_non_arg_varnames = rearrange_by_indices(non_arg_varnames, early_indices)
     # Build co_varnames for the new code object. This should consist of
     # the entirety of co_varnames with new_locals spliced in after the
     # arguments
     new_locals_len = len(new_locals)
     varnames = (
-        co_varnames[:co_argcount] + new_locals + co_varnames[co_argcount:])
+        co_varnames[:co_argcount] + new_locals + tuple(rearranged_non_arg_varnames))
 
     # Build the dictionary that maps indices of entries in the old co_varnames
     # to their indices in the new co_varnames
     range1, range2 = xrange(co_argcount), xrange(co_argcount, len(co_varnames))
     varname_translations = dict((i, i) for i in range1)
-    varname_translations.update((i, i + new_locals_len) for i in range2)
+    varname_translations.update((i, varnames.index(co_varnames[i])) for i in range2)
 
     # Build the dictionary that maps indices of deleted entries of co_names
     # to their indices in the new co_varnames
@@ -94,6 +135,7 @@ def append_arguments(code_obj, new_local
     modified = []
     drop_future_cache = False
     for inst in instructions(code_obj):
+        # print(inst.opname + ' - ' + str(inst.line_number))
         if is_new_bytecode and inst.opname == "CACHE":
             assert inst.arg == 0
             if not drop_future_cache:
@@ -124,9 +166,18 @@ def append_arguments(code_obj, new_local
                 arg = tgt
             else:
                 raise(ValueError("a name was lost in translation last instruction %s" % str(inst)))
+        elif inst.opcode in (LOAD_FAST_LOAD_FAST, STORE_FAST_LOAD_FAST, STORE_FAST_STORE_FAST):
+            old_first_arg = inst.arg >> 4 & 0xF
+            old_second_arg = inst.arg & 0xF
+            new_first_arg = varname_translations[old_first_arg]
+            new_second_arg = varname_translations[old_second_arg]
+            arg = new_first_arg << 4 | new_second_arg
         # If it accesses co_varnames or co_names then update its argument.
         elif inst.opcode in opcode.haslocal:
-            arg = varname_translations[inst.arg]
+            if arg < len(varname_translations): # Otherwise cellvar whose arg gets incremented by new_locals_len
+                arg = varname_translations[inst.arg]
+            else:
+                arg += new_locals_len
         elif inst.opcode in opcode.hasname:
             # for example STORE_GLOBAL
             arg = name_translations[inst.arg]
@@ -135,6 +186,12 @@ def append_arguments(code_obj, new_local
             if inst.argval not in code_obj.co_varnames[:code_obj.co_argcount]:  # we do not need to remap existing arguments, they are not shifted by new ones.
                 arg = inst.arg + len(new_locals)
         modified.extend(write_instruction(op, arg))
+        if hasattr(inst, 'end_offset'): # Assume otherwise instructions will have explicit CACHE entries
+            for inline_cache in range(int((inst.end_offset - inst.cache_offset) / 2)):
+                if not drop_future_cache:
+                    modified.extend(write_instruction(0, 0))
+                else:
+                    modified.extend(write_instruction(dis.opmap["NOP"], 0))
     code = bytes(modified)
     args = (co_argcount + new_locals_len, 0,
             code_obj.co_nlocals + new_locals_len, code_obj.co_stacksize,
@@ -229,9 +286,16 @@ def check(code_obj):
     pos_to_inst = {}
     bytelist = []
 
+    offset = 0
+    instruction_bytes = None
     for inst in insts:
         pos_to_inst[len(bytelist)] = inst
-        bytelist.extend(write_instruction(inst.opcode, inst.arg))
+        if instruction_bytes:
+            for gap_byte in range(inst.offset - offset - len(instruction_bytes)):
+                bytelist.append(0)
+        offset = inst.offset
+        instruction_bytes = write_instruction(inst.opcode, inst.arg)
+        bytelist.extend(instruction_bytes)
     new_bytecode = bytes(bytelist)
     if new_bytecode != old_bytecode:
         print(new_bytecode)
openSUSE Build Service is sponsored by