Find current function call in the byte code.

def _find_current_fn_call(co, lasti):
  """Find current function call in the byte code."""
  qj._DEBUG_QJ and qj.LOG_FN('co = {}'.format(co))

  if sys.version_info[0] < 3:
    stack = _build_instruction_stack(co, lasti)
  else:
    stack = _build_instruction_stack3(co, lasti)

  source_lines, source_offset = inspect.getsourcelines(co)
  source_lines = [l.strip() for l in source_lines]

  # Apply stack effects backwards until we arrive at the stack entries for a complete function call
  fn_stack = _collect_pops(stack[:-1], stack[-1].stack_depth, [], 0)

  if not fn_stack and stack[-1].stack_depth == 0:
    # The function call took 0 arguments, so return early with a special string.
    return '<empty log>'

  qj._DEBUG_QJ and qj.LOG_FN('collected fn_stack:\n%s\n\n' % '\n'.join(str(se) for se in fn_stack))

  # Prepare to annotate the stack with extra symbols, and filter out MAKE_FUNCTION and MAKE_CLOSURE calls, which are no longer needed.
  annotate_stack = [se for se in fn_stack if not se.opname.startswith('MAKE_')]
  annotate_stack.reverse()
  _annotate_fn_args(annotate_stack, stack[-1].opname, stack[-1].oparg, -1, False)

  qj._DEBUG_QJ and qj.LOG_FN('annotated fn_stack:\n%s\n\n' % '\n'.join(str(se) for se in fn_stack))

  # Find the range of lines to search over.
  min_l = stack[-1].curr_l
  max_l = stack[0].curr_l
  for se in fn_stack:
    min_l = min(min_l, se.curr_l)
    max_l = max(max_l, se.curr_l)

  qj._DEBUG_QJ and qj.LOG_FN('source lines range: %d -> %d (indices: %d, %d)' % (min_l, max_l, min_l - source_offset, max_l - source_offset + 1))
  source_chunk = ' '.join(
      [l for l in source_lines[min_l - source_offset:max_l - source_offset + 1] if l and not l.startswith('#')])

  qj._DEBUG_QJ and qj.LOG_FN('source_chunk: %r' % source_chunk)

  # Build up all of the tokens for the function call.
  tokens = []
  for se in fn_stack:
    opname = se.opname
    for oparg_repr in se.oparg_repr[::-1]:
      # Clean up the tokens
      if not oparg_repr:
        continue
      oparg_repr = re.escape(oparg_repr.replace('\n', '\\n'))
      tokens.append(oparg_repr)

  tokens.reverse()
  qj._DEBUG_QJ and qj.LOG_FN('extracted tokens: {}'.format(tokens))
  if qj._DEBUG_QJ:
    assert tokens

  # Add tokens for the function call we're extracting.
  tokens = ['\\('] + tokens + ['\\)']

  reg = '\b*?.*?\b*?'.join(tokens)
  qj._DEBUG_QJ and qj.LOG_FN(reg)

  # Search for the function call using the full set of tokens.
  # Expand the search with source lines after the current set if we don't find a match.
  shortest_match = None
  match_attempts = 0
  max_match_attempts = 10
  while not shortest_match and match_attempts < max_match_attempts:
    (shortest_match, _, _) = (
        _find_earliest_shortest_match(source_chunk, reg, 0, len(source_chunk), num_attempts=10))
    if not shortest_match:
      match_attempts += 1

      min_l -= 1
      prev_line = ''
      while not prev_line and min_l - source_offset >= 0 and min_l - source_offset < len(source_lines):
        prev_line = source_lines[min_l - source_offset]
        if prev_line.startswith('#'):
          prev_line = ''
        if not prev_line:
          min_l -= 1
      prev_line += ' ' if prev_line else ''
      source_chunk = prev_line + source_chunk

      max_l += 1
      next_line = ''
      while not next_line and max_l - source_offset >= 0 and max_l - source_offset < len(source_lines):
        next_line = source_lines[max_l - source_offset]
        if next_line.startswith('#'):
          next_line = ''
        if not next_line:
          max_l += 1
      next_line = (' ' if next_line else '') + next_line
      source_chunk += next_line

  # Return the string for the function call
  if qj._DEBUG_QJ:
    assert shortest_match
  if shortest_match is not None:
    match = shortest_match.group(0)
    if qj._DEBUG_QJ:
      assert match.startswith('(') and match.endswith(')')
    # Do some parentheses cleanup.
    if match.startswith('('):
      match = match[1:]
    lparens = match.count('(')
    rparens = match.count(')')
    if lparens > rparens:
      match += ')' * (lparens - rparens)
    elif lparens < rparens:
      rparens_to_remove = rparens - lparens
      while len(match) and match[-1] == ')' and rparens_to_remove > 0:
        match = match[:-1]
        rparens_to_remove -= 1
    match = match.strip()
    return match
  else:
    return ''