Iterates over *tokens*, which must be an equivalent output to what
    tokenize.generate_tokens() produces, replacing the given identifier name
    (*replace*) by calling *obfunc* on each token with the following parameters:

        - **module:**       The name of the script we're currently obfuscating.
        - **tokens:**       The current list of all tokens.
        - **index:**        The current position.
        - **replace:**      The token string that we're replacing.
        - **replacement:**  A randomly generated, unique value that will be used to replace, *replace*.
        - **right_of_equal:**   A True or False value representing whether or not the token is to the right of an equal sign.  **Note:** This gets reset to False if a comma or open paren are encountered.
        - **inside_parens:**    An integer that is incremented whenever an open paren is encountered and decremented when a close paren is encountered.
        - **inside_function:**  If not False, the name of the function definition we're inside of (used in conjunction with *keyword_args* to determine if a safe replacement can be made).

    *obfunc* is expected to return the token string if that token can be safely
    obfuscated **or** one of the following optional values which will instruct
    find_obfuscatables() how to proceed:

        - **'__open_paren__'**        Increment the inside_parens value
        - **'__close_paren__'**       Decrement the inside_parens value
        - **'__comma__'**             Reset the right_of_equal value to False
        - **'__right_of_equal__'**    Sets the right_of_equal value to True

    **Note:** The right_of_equal and the inside_parens values are reset whenever a NEWLINE is encountered.

    When obfuscating a list of files, *table* is used to keep track of which
    obfuscatable identifiers are which inside each resulting file.  It must be
    an empty dictionary that will be populated like so::

        {orig_name: obfuscated_name}

    This *table* of "what is what" will be used to ensure that references from
    one script/module that call another are kept in sync when they are replaced
    with obfuscated values.

def replace_obfuscatables(module, tokens, obfunc, replace, name_generator, table=None):
    """
    Iterates over *tokens*, which must be an equivalent output to what
    tokenize.generate_tokens() produces, replacing the given identifier name
    (*replace*) by calling *obfunc* on each token with the following parameters:

        - **module:**       The name of the script we're currently obfuscating.
        - **tokens:**       The current list of all tokens.
        - **index:**        The current position.
        - **replace:**      The token string that we're replacing.
        - **replacement:**  A randomly generated, unique value that will be used to replace, *replace*.
        - **right_of_equal:**   A True or False value representing whether or not the token is to the right of an equal sign.  **Note:** This gets reset to False if a comma or open paren are encountered.
        - **inside_parens:**    An integer that is incremented whenever an open paren is encountered and decremented when a close paren is encountered.
        - **inside_function:**  If not False, the name of the function definition we're inside of (used in conjunction with *keyword_args* to determine if a safe replacement can be made).

    *obfunc* is expected to return the token string if that token can be safely
    obfuscated **or** one of the following optional values which will instruct
    find_obfuscatables() how to proceed:

        - **'__open_paren__'**        Increment the inside_parens value
        - **'__close_paren__'**       Decrement the inside_parens value
        - **'__comma__'**             Reset the right_of_equal value to False
        - **'__right_of_equal__'**    Sets the right_of_equal value to True

    **Note:** The right_of_equal and the inside_parens values are reset whenever a NEWLINE is encountered.

    When obfuscating a list of files, *table* is used to keep track of which
    obfuscatable identifiers are which inside each resulting file.  It must be
    an empty dictionary that will be populated like so::

        {orig_name: obfuscated_name}

    This *table* of "what is what" will be used to ensure that references from
    one script/module that call another are kept in sync when they are replaced
    with obfuscated values.
    """
    # Pretend the first line is '#\n':
    skip_line = False
    skip_next = False
    right_of_equal = False
    inside_parens = 0
    inside_function = False
    indent = 0
    function_indent = 0
    replacement = next(name_generator)
    for index, tok in enumerate(tokens):
        token_type = tok[0]
        token_string = tok[1]
        if token_type == tokenize.NEWLINE:
            skip_line = False
            right_of_equal = False
            inside_parens = 0
        elif token_type == tokenize.INDENT:
            indent += 1
        elif token_type == tokenize.DEDENT:
            indent -= 1
            if inside_function and function_indent == indent:
                function_indent = 0
                inside_function = False
        if token_string == "def":
            function_indent = indent
            function_name = tokens[index+1][1]
            inside_function = function_name
        result = obfunc(
            tokens,
            index,
            replace,
            replacement,
            right_of_equal,
            inside_parens,
            inside_function
        )
        if result:
            if skip_next:
                skip_next = False
            elif skip_line:
                pass
            elif result == '__skipline__':
                skip_line = True
            elif result == '__skipnext__':
                skip_next = True
            elif result == '__open_paren__':
                right_of_equal = False
                inside_parens += 1
            elif result == '__close_paren__':
                inside_parens -= 1
            elif result == '__comma__':
                right_of_equal = False
            elif result == '__right_of_equal__':
                # We only care if we're right of the equal sign outside of
                # parens (which indicates arguments)
                if not inside_parens:
                    right_of_equal = True
            else:
                if table: # Save it for later use in other files
                    combined_name = "%s.%s" % (module, token_string)
                    try: # Attempt to use an existing value
                        tokens[index][1] = table[0][combined_name]
                    except KeyError: # Doesn't exist, add it to table
                        table[0].update({combined_name: result})
                        tokens[index][1] = result
                else:
                    tokens[index][1] = result