def _collect_pops(stack, depth, pops, skip):
  """Recursively collects stack entries off the top of the stack according to the stack entry's depth."""
  if depth >= 0:
    return pops

  set_current_depth_after_recursion = False
  set_skip_for_current_entry_children = False
  set_skip_after_current_entry = False
  extract_next_tokens = False
  expect_extracted_tokens = []

  se = stack.pop()
  pops_len = len(pops)
  if (pops_len > 1
      and se.opname == 'BUILD_TUPLE'
      and pops[-1].opname == 'LOAD_CONST'
      and pops[-1].oparg_repr[0] in ['lambda', '{', '(', '[']
      and pops[-2].opname in ['MAKE_CLOSURE', 'MAKE_FUNCTION']):
    # Skip BUILD_TUPLE and its children if they are storing arguments for a closure, since those don't show up in the code.
    skip = -se.stack_depth + 1

  if (pops_len > 2
      and sys.version_info[0] >= 3
      and se.opname == 'BUILD_TUPLE'
      and pops[-1].opname == 'LOAD_CONST'
      and pops[-1].oparg_repr[0] in ['lambda', '{', '(', '[']
      and pops[-2].opname == 'LOAD_CONST'
      and pops[-3].opname in ['MAKE_CLOSURE', 'MAKE_FUNCTION']):
    # Skip BUILD_TUPLE and its children if they are storing arguments for a closure, since those don't show up in the code.
    skip = -se.stack_depth + 1

  if (pops_len > 0
      and se.opname == 'GET_ITER'
      and pops[-1].opname == 'CALL_FUNCTION'):
    # CALL_FUNCTION followed by GET_ITER means we are calling one of the comprehensions and we are about to load its arguments.
    # The CALL_FUNCTION at the top of the stack should be invisible, since it expects a ')' which won't appear in the code.
    pops[-1].oparg_repr = ['']
    # We need to extract the arguments that we're about to load so that we can store their tokens inside of the upcoming comprehension.
    extract_next_tokens = -1

  if (len(stack)
      and se.opname == 'BUILD_TUPLE_UNPACK_WITH_CALL'):
    extract_next_tokens = se.stack_depth
    expect_extracted_tokens = [
        # Expect LOAD_FAST as the first element (required=True), and prepend its oparg_repr with '*'.
        (0, 'LOAD_FAST', True, 'prepend', ['*']),
        # Expect BUILD_TUPLE as the last stack token extracted (required=False) and replace its oparg_repr with ''.
        (abs(extract_next_tokens) - 1, 'BUILD_TUPLE', False, 'replace', [''])
    ]
    set_current_depth_after_recursion = se.stack_depth
    se.stack_depth = 0

  if (pops_len > 0
      and sys.version_info[0] >= 3
      and se.opname == 'LOAD_CONST'
      and pops[-1].opname == 'MAKE_FUNCTION'):
    # In python 3, MAKE_FUNCTION followed by LOAD_CONST is loading the name of the function, which won't appear in the code.
    se.oparg_repr = ['']
    # Additionally, this entry shouldn't impact future stack computations, as MAKE_FUNCTION will be removed.
    set_current_depth_after_recursion = 0

  if pops_len and pops[-1].opname == 'LIST_APPEND':
    # Skip all but the first stack entry of list comprehensions. Sets the skip value to be all remaining stack entries.
    # The BUILD_LIST check below will disable skip at the right time.
    set_skip_after_current_entry = len(stack)

  if skip > 0 and se.opname == 'BUILD_LIST' and se.stack_depth == 0:
    # If we're in skip mode and we just hit what might be the beginning of a list comprehension, check for a LIST_APPEND in the current pops.
    for popped_se in pops[::-1]:
      if popped_se.opname == 'LIST_APPEND':
        skip = 0
        break

  children_skip = skip
  if (se.opname.startswith('UNARY_')
      or (se.opname.startswith('BINARY_') and se.opname != 'BINARY_SUBSCR')
      or se.opname == 'SLICE+2'
      or se.opname == 'SLICE+3'
      or se.opname == 'COMPARE_OP'):
    # Unary and binary ops come after their operand(s) on the stack, but before (or between) their operand(s) in code, so we need to reverse that.

    if set_skip_for_current_entry_children or skip > 0:
      children_skip = 1
    pops = _collect_pops(stack, -1, pops, children_skip)

    if skip <= 0:
      pops.append(se)
      qj._DEBUG_QJ and qj.LOG_FN('added se: %r' % se)
    else:
      qj._DEBUG_QJ and qj.LOG_FN('(skipping se: %r %r)' % (se.opname, se.oparg_repr))

    popped_depth = se.stack_depth + 1
  else:
    # Non prefix/infix ops -- their representations come after their children in code, or they don't have children.
    if skip <= 0:
      pops.append(se)
      qj._DEBUG_QJ and qj.LOG_FN('added se: %r' % se)
    else:
      qj._DEBUG_QJ and qj.LOG_FN('(skipping se: %r %r)' % (se.opname, se.oparg_repr))

    if ((se.stack_depth < 0
         and se.opname != 'BUILD_SLICE'
         and se.opname.startswith('BUILD_'))
        or se.stack_depth >= 0):
      next_depth = se.stack_depth
    else:
      next_depth = se.stack_depth - 1

    if set_skip_for_current_entry_children or skip > 0:
      children_skip = abs(next_depth)
    if se.opname == 'BUILD_SLICE':
      # BUILD_SLICE's arguments need to be collected, as missing args are replaced with Nones which don't appear in the code.
      slice_pops = _collect_pops(stack, next_depth, [], children_skip)
      added_colon = 0
      for slice_se in slice_pops:
        if slice_se.opname == 'LOAD_CONST' and slice_se.oparg_repr[0] == 'None':
          if added_colon >= 1:
            slice_se.oparg_repr = ['']
          else:
            slice_se.oparg_repr = [':']
            added_colon += 1
        pops.append(slice_se)
    else:
      pops = _collect_pops(stack, next_depth, pops, children_skip)

    # BUILD_LIST 0 marks the start of a list comprehension, but we need it to consume a slot on the stack.
    if se.stack_depth == 0 and se.opname != 'BUILD_LIST':
      popped_depth = 0
    else:
      popped_depth = 1

  tokens = []
  if extract_next_tokens < 0:
    tokens = _collect_pops(stack, extract_next_tokens, [], skip)
    for index, expected_token, required, fixup_type, fixup_value in expect_extracted_tokens:
      if qj._DEBUG_QJ:
        assert (index < 0 and index + len(tokens) > 0) or 0 <= index < len(tokens)
        if required:
          assert tokens[index].opname == expected_token
      if (index < 0 and index + len(tokens) > 0) or 0 <= index < len(tokens) and tokens[index].opname == expected_token:
        if fixup_type == 'prepend':
          tokens[index].oparg_repr = fixup_value + tokens[index].oparg_repr
        elif fixup_type == 'replace':
          tokens[index].oparg_repr = fixup_value
    tokens.reverse()
    popped_depth -= extract_next_tokens

  if children_skip > 0:
    skip -= popped_depth

  if set_skip_after_current_entry > 0:
    skip = set_skip_after_current_entry + max(0, skip)

  pops = _collect_pops(stack, depth + popped_depth, pops, skip)

  if len(tokens):  # pylint: disable=g-explicit-length-test
    target_se = pops[-1]
    target_se.children.append(tokens)
    target_se.oparg_repr = target_se.oparg_repr[:1] + [t for token in tokens for t in token.oparg_repr] + target_se.oparg_repr[1:]

  if set_current_depth_after_recursion is not False:
    se.stack_depth = set_current_depth_after_recursion

  return pops
def _collect_pops ( stack , depth , pops , skip ) : if depth >= 0 : return pops set_current_depth_after_recursion = False set_skip_for_current_entry_children = False set_skip_after_current_entry = False extract_next_tokens = False expect_extracted_tokens = [ ] se = stack . pop ( ) pops_len = len ( pops ) if ( pops_len > 1 and se . opname == 'BUILD_TUPLE' and pops [ - 1 ] . opname == 'LOAD_CONST' and pops [ - 1 ] . oparg_repr [ 0 ] in [ 'lambda' , '{' , '(' , '[' ] and pops [ - 2 ] . opname in [ 'MAKE_CLOSURE' , 'MAKE_FUNCTION' ] ) : skip = - se . stack_depth + 1 if ( pops_len > 2 and sys . version_info [ 0 ] >= 3 and se . opname == 'BUILD_TUPLE' and pops [ - 1 ] . opname == 'LOAD_CONST' and pops [ - 1 ] . oparg_repr [ 0 ] in [ 'lambda' , '{' , '(' , '[' ] and pops [ - 2 ] . opname == 'LOAD_CONST' and pops [ - 3 ] . opname in [ 'MAKE_CLOSURE' , 'MAKE_FUNCTION' ] ) : skip = - se . stack_depth + 1 if ( pops_len > 0 and se . opname == 'GET_ITER' and pops [ - 1 ] . opname == 'CALL_FUNCTION' ) : pops [ - 1 ] . oparg_repr = [ '' ] extract_next_tokens = - 1 if ( len ( stack ) and se . opname == 'BUILD_TUPLE_UNPACK_WITH_CALL' ) : extract_next_tokens = se . stack_depth expect_extracted_tokens = [ ( 0 , 'LOAD_FAST' , True , 'prepend' , [ '*' ] ) , ( abs ( extract_next_tokens ) - 1 , 'BUILD_TUPLE' , False , 'replace' , [ '' ] ) ] set_current_depth_after_recursion = se . stack_depth se . stack_depth = 0 if ( pops_len > 0 and sys . version_info [ 0 ] >= 3 and se . opname == 'LOAD_CONST' and pops [ - 1 ] . opname == 'MAKE_FUNCTION' ) : se . oparg_repr = [ '' ] set_current_depth_after_recursion = 0 if pops_len and pops [ - 1 ] . opname == 'LIST_APPEND' : set_skip_after_current_entry = len ( stack ) if skip > 0 and se . opname == 'BUILD_LIST' and se . stack_depth == 0 : for popped_se in pops [ : : - 1 ] : if popped_se . opname == 'LIST_APPEND' : skip = 0 break children_skip = skip if ( se . opname . startswith ( 'UNARY_' ) or ( se . opname . startswith ( 'BINARY_' ) and se . opname != 'BINARY_SUBSCR' ) or se . opname == 'SLICE+2' or se . opname == 'SLICE+3' or se . opname == 'COMPARE_OP' ) : if set_skip_for_current_entry_children or skip > 0 : children_skip = 1 pops = _collect_pops ( stack , - 1 , pops , children_skip ) if skip <= 0 : pops . append ( se ) qj . _DEBUG_QJ and qj . LOG_FN ( 'added se: %r' % se ) else : qj . _DEBUG_QJ and qj . LOG_FN ( '(skipping se: %r %r)' % ( se . opname , se . oparg_repr ) ) popped_depth = se . stack_depth + 1 else : if skip <= 0 : pops . append ( se ) qj . _DEBUG_QJ and qj . LOG_FN ( 'added se: %r' % se ) else : qj . _DEBUG_QJ and qj . LOG_FN ( '(skipping se: %r %r)' % ( se . opname , se . oparg_repr ) ) if ( ( se . stack_depth < 0 and se . opname != 'BUILD_SLICE' and se . opname . startswith ( 'BUILD_' ) ) or se . stack_depth >= 0 ) : next_depth = se . stack_depth else : next_depth = se . stack_depth - 1 if set_skip_for_current_entry_children or skip > 0 : children_skip = abs ( next_depth ) if se . opname == 'BUILD_SLICE' : slice_pops = _collect_pops ( stack , next_depth , [ ] , children_skip ) added_colon = 0 for slice_se in slice_pops : if slice_se . opname == 'LOAD_CONST' and slice_se . oparg_repr [ 0 ] == 'None' : if added_colon >= 1 : slice_se . oparg_repr = [ '' ] else : slice_se . oparg_repr = [ ':' ] added_colon += 1 pops . append ( slice_se ) else : pops = _collect_pops ( stack , next_depth , pops , children_skip ) if se . stack_depth == 0 and se . opname != 'BUILD_LIST' : popped_depth = 0 else : popped_depth = 1 tokens = [ ] if extract_next_tokens < 0 : tokens = _collect_pops ( stack , extract_next_tokens , [ ] , skip ) for index , expected_token , required , fixup_type , fixup_value in expect_extracted_tokens : if qj . _DEBUG_QJ : assert ( index < 0 and index + len ( tokens ) > 0 ) or 0 <= index < len ( tokens ) if required : assert tokens [ index ] . opname == expected_token if ( index < 0 and index + len ( tokens ) > 0 ) or 0 <= index < len ( tokens ) and tokens [ index ] . opname == expected_token : if fixup_type == 'prepend' : tokens [ index ] . oparg_repr = fixup_value + tokens [ index ] . oparg_repr elif fixup_type == 'replace' : tokens [ index ] . oparg_repr = fixup_value tokens . reverse ( ) popped_depth -= extract_next_tokens if children_skip > 0 : skip -= popped_depth if set_skip_after_current_entry > 0 : skip = set_skip_after_current_entry + max ( 0 , skip ) pops = _collect_pops ( stack , depth + popped_depth , pops , skip ) if len ( tokens ) : target_se = pops [ - 1 ] target_se . children . append ( tokens ) target_se . oparg_repr = target_se . oparg_repr [ : 1 ] + [ t for token in tokens for t in token . oparg_repr ] + target_se . oparg_repr [ 1 : ] if set_current_depth_after_recursion is not False : se . stack_depth = set_current_depth_after_recursion return pops
Recursively collects stack entries off the top of the stack according to the stack entry's depth.