# Global Python variables

global python3_vars_seen			# which vars we have seen
global python3_frame_n = 0			# set by iterate_over_frames
global python3_frame = 0			# set by python3_function_entry

# FUNCTION P3_GET_TYPE
# OBJ: PyObject
# RETURNS: string representation of type

function p3_get_type:string (obj) {
    if (obj == 0)
	return ""
    ob_type = @cast (obj, "PyObject", "PYTHON3")->ob_type
    if (ob_type == 0)
	return ""
    ob_type_name = @cast (ob_type, "PyTypeObject", "PYTHON3")->tp_name
    return set_string (ob_type_name)
}

# FUNCTION GET_FILENAME
# F_CODE: The python frame code whose filename we are retrieving

function p3_get_filename (f_code)
{
    co_filename = @cast (f_code, "PyCodeObject", "PYTHON3")->co_filename;
    return get_unicode (co_filename)
}


# FUNCTION P3_GET_NAME
# F_CODE: The python frame code whose function name we are retrieving

function p3_get_name (f_code)
{
    co_name = @cast (f_code, "PyCodeObject", "PYTHON3")->co_name;
    return get_unicode (co_name)
}

# FUNCTION P3_SET_STRING
# STR_P: pointer to string
# RETURNS: string as a stap string

function p3_set_string (str_p) {
    if (str_p == 0)
	return ""
    else
	return user_string (str_p)
}

function get_unicode:string (utf16) {
    if (utf16 == 0)
	return ""
    length = @cast (utf16, "PyUnicodeObject", "PYTHON3")->length;
    unip = @cast (utf16, "PyUnicodeObject", "PYTHON3")->str;
    if (length == 0 || unip == 0)
        return ""
    newstr = ""
    
    # grab every other character
    # ??? Do we need general purpose UTF routines?
    for (i = 0; i < length; i++) {
        if (user_char(unip + (i * 2)) == 0)
	    break
	val = user_string_n(unip + (i * 2), 1)
	newstr = newstr . substr(val,0,1)
    }
    return newstr
}


# FUNCTION P3_GET_SEQUENCE_ITEM
# TUPLE: array of PyObject
# I: index to retrieve 
# SEQ_TYPE: PyTupleObject or PyListObject
# RETURNS: array entry PyObject

function p3_get_sequence_item (tuple, i, seq_type) {
    if (seq_type == "tuple")
	ob_items = @cast (tuple, "PyTupleObject", "PYTHON3")->ob_item;
    else if (seq_type == "list")
	ob_items = @cast (tuple, "PyListObject", "PYTHON3")->ob_item;

    ob_item = user_long (ob_items + (i * %{ sizeof (intptr_t) %}))
    return ob_item
}

# FUNCTION P3_DISPLAY_VALUE
# VALUE: PyObject
# PREFIX: prefix string to display
# SUFFIX: suffix string to display

function p3_display_value (value, prefix, suffix) {
    if (value == 0) {
	return 0
    }
    value_type_str = p3_get_type (value)
    if (value_type_str == "str") {
	value_str = get_unicode (value)
	printf ("%s\"%s\"%s", prefix, value_str, suffix)
    }
    else if (value_type_str == "int") {
	if (@cast (value, "PyLongObject", "PYTHON3")->ob_base->ob_size == 1)
	    printf ("%s%d%s", prefix, user_int(@cast (value, "PyLongObject", "PYTHON3")[0]->ob_digit), suffix)
	else if (@cast (value, "PyLongObject", "PYTHON3")->ob_base->ob_size == 2) {
	    printf ("%s%#lx", prefix, user_int(@cast (value, "PyLongObject", "PYTHON3")[0]->ob_digit + 4) >> 2)
	    printf ("%x%s", user_int(@cast (value, "PyLongObject", "PYTHON3")[0]->ob_digit) | ((user_int(@cast (value, "PyLongObject", "PYTHON3")[0]->ob_digit + 4) << 30) & 0xffffffff), suffix)
	}
    }
    else if (value_type_str == "tuple" || value_type_str == "list") {
	n = @cast (value, "PyTupleObject", "PYTHON3")->ob_base->ob_size;
	if (value_type_str == "list") printf (" = [")
	else printf (" = ")
	for (i = 0; i < n; i++) 
	    p3_display_value (p3_get_sequence_item (value, i, value_type_str), " ", ",")
	if (value_type_str == "list") printf ("]")
    }
    else if (value_type_str == "set") {
	printf (" = {")
	n = @cast (value, "PySetObject", "PYTHON3")->used;
	for (i = 0; i <= n; i++)
	    p3_display_value (get_set_item (value, i), " ", ",")
	printf ("}")
    }
    else if (value_type_str == "dict") {
	printf (" = {")
	n = @cast (value, "PyDictObject", "PYTHON3")->ma_used;
	for (i = 0; i <= n; i++) {
	    dict_hash = p3_get_dict_hash (value, i)
	    dict_key = p3_get_dict_key (value, i)
	    dict_value = p3_get_dict_value (value, i)
	    if (dict_hash == 0 && dict_key == 0)
		continue
	    p3_display_value (dict_key, " ", ":")
	    p3_display_value (dict_value, "", "")
	}
	printf (" }")
    }
}

# FUNCTION P3_GET_SET_ITEM
# SET: PySetObject
# I: set index to retrieve
# RETURNS: set entry PyObject

function p3_get_set_item (set, i) {
    entries = @cast (set, "PySetObject", "PYTHON3")->table;
    n = @cast (set, "PySetObject", "PYTHON3")->used;
    if (i > n)
	return 0
  
    return @cast (entries, "setentry", "PYTHON3")[i]->key
}

# FUNCTION P3_GET_DICT_HASH
# DICT: PySetObject
# I: set index to retrieve
# RETURNS: DICT_HASH

function p3_get_dict_hash (dict, i) {
    entries = @cast (dict, "PyDictObject", "PYTHON3")->ma_table;
    n = @cast (dict, "PyDictObject", "PYTHON3")->ma_used;
    if (i > n)
	return 0
  
    return @cast (entries, "PyDictEntry", "PYTHON3")[i]->me_hash
}

# FUNCTION P3_GET_DICT_KEY
# DICT: PySetObject
# I: set index to retrieve
# RETURNS: DICT_KEY

function p3_get_dict_key (dict, i) {
    entries = @cast (dict, "PyDictObject", "PYTHON3")->ma_table;
    n = @cast (dict, "PyDictObject", "PYTHON3")->ma_used;
    if (i > n)
	return 0
  
    return @cast (entries, "PyDictEntry", "PYTHON3")[i]->me_key
}

# FUNCTION P3_GET_DICT_VALUE
# DICT: PySetObject
# I: set index to retrieve
# RETURNS: DICT_VALUE

function p3_get_dict_value (dict, i) {
    entries = @cast (dict, "PyDictObject", "PYTHON3")->ma_table;
    n = @cast (dict, "PyDictObject", "PYTHON3")->ma_used;
    if (i > n)
	return 0
  
    return @cast (entries, "PyDictEntry", "PYTHON3")[i]->me_value;
}

# FUNCTION P3_DISPLAY_DICT_VARIABLE
# DICT: local or global variable name dictionary
# VARIABLE: variable to retrieve
# NAMESPACE: local or global

function p3_display_dict_variable (dict, variable, namespace, co_name_str, co_filename_str) {
    n = @cast (dict, "PyDictObject", "PYTHON3")->ma_used;
    for (i = 0; i < n; i++) {
	dict_key = p3_get_dict_key (dict, i)
	dict_value = p3_get_dict_value (dict, i)
	dict_hash = p3_get_dict_hash (dict, i)
	if (dict_hash == 0 || dict_value == 0)
	    continue
#	key_str = p3_set_string (@cast (dict_key, "PyUnicodeObject", "PYTHON3")->str)
	key_str = get_unicode (dict_key)
	key_type_str = p3_get_type (dict_key)

	if (wildcard_match (key_str, variable)) {
	    foreach ([var, file] in python3_vars_seen) {
		if (var == key_str
		    && file == co_filename_str)
		    return 0
	    }
	    printf ("%s %s %s", namespace, key_type_str, key_str)
	    printf (" in %s at %s", co_name_str, co_filename_str)
	    p3_display_value (dict_value, " = ", " ")
	    printf ("\n")
	    python3_vars_seen[key_str, co_filename_str] = co_name_str
	}
    }
}

# FUNCTION ITERATE_OVER_FRAMES
# FRAME: Pointer to a stack frame
# FRAME_N: The frame number of this stack frame

function python3_iterate_over_frames (frame, frame_n)
{
    if (frame_n != 0) {
	python3_frame_n += 1
	frame =  @cast (frame, "PyFrameObject", "PYTHON3")->f_back
    }
    else
	python3_frame_n = 0
    return frame
}

    
# FUNCTION BACKTRACE
# FRAME: Pointer to first stack frame
# MATCH_FILENAME: Pathnames containing this string are ignored.  Default is "/lib/" to
#     ignore system python libraries

function python3_backtrace (frame, match_filename)
{
    f_code = @cast (frame, "PyFrameObject", "PYTHON3")->f_code;
    co_filename_str = p3_get_filename (f_code)
    if (isinstr (co_filename_str, "/lib/") == 1)
	return 0
    if (match_filename != "" && isinstr (co_filename_str, match_filename) == 0)
	return 0
    co_name_str = p3_get_name (f_code)
    co_firstlineno = @cast (f_code, "PyCodeObject", "PYTHON3")->co_firstlineno;

    printf ("#%d %s ", python3_frame_n, co_name_str)

    co_varnames = @cast (f_code, "PyCodeObject", "PYTHON3")->co_varnames;
    co_argcount = @cast (f_code, "PyCodeObject", "PYTHON3")->co_argcount;
    f_localsplus = @cast (frame, "PyFrameObject", "PYTHON3")->f_localsplus;
    for (i = 0; i < co_argcount; i++) {
	if (i == 0) print ("(");
	arg_name_str = get_unicode (p3_get_sequence_item (co_varnames, i, "tuple"))
	arg_value = user_long (f_localsplus + (i * %{ sizeof (intptr_t) %}))
	arg_type_name_str = p3_get_type (arg_value)
	printf ("%s:%s ", arg_name_str, arg_type_name_str)
    }

    if (co_argcount) printf (")");

    printf (" at %s:%d\n", co_filename_str, co_firstlineno)
}
    
# FUNCTION GET_VARIABLE
# FRAME: Pointer to first stack frame
# MATCH_ARGS: Match function arguments
# VARIABLE: Variable wildcard to be matched.  Default is "*" to match all variables.
# MATCH_FILENAME: Pathnames containing this string are ignored.  Default is "/lib/" to
#     ignore system python libraries

function python3_get_variable (frame, match_args, variable, match_filename)
{
    f_code = @cast (frame, "PyFrameObject", "PYTHON3")->f_code;
    co_filename_str = p3_get_filename (f_code)
    if (isinstr (co_filename_str, "/lib/") == 1)
	return 0
    if (match_filename != "" && isinstr (co_filename_str, match_filename) == 0)
	return 0
    co_name_str = p3_get_name (f_code)
    
    f_globals = @cast (frame, "PyFrameObject", "PYTHON3")->f_globals;
    if (f_globals != 0) {
	p3_display_dict_variable (f_globals, variable, "global", co_name_str, co_filename_str)
    }
    f_locals = @cast (frame, "PyFrameObject", "PYTHON3")->f_locals;
    if (f_locals != 0) {
	p3_display_dict_variable (f_locals, variable, "local", co_name_str, co_filename_str)
    }

    co_varnames = @cast (f_code, "PyCodeObject", "PYTHON3")->co_varnames;
    co_argcount = @cast (f_code, "PyCodeObject", "PYTHON3")->co_argcount;
    f_localsplus = @cast (frame, "PyFrameObject", "PYTHON3")->f_localsplus;
    ob_size = @cast (co_varnames, "PyTypeObject", "PYTHON3")->ob_base->ob_size;
    # iterate through the local variable list
    for (i = 0; i < ob_size; i++) {
	arg_name_str = get_unicode (p3_get_sequence_item (co_varnames, i, "tuple"))
	if (! wildcard_match (arg_name_str, variable))
	    continue
	arg_value = user_long (f_localsplus + (i * %{ sizeof (intptr_t) %}))
	arg_type_name_str = p3_get_type (arg_value)
	next_var = 0
	foreach ([var, file] in python3_vars_seen)
	if (var == arg_name_str && file == co_filename_str)
	    next_var = 1
	if (next_var || arg_type_name_str == "")
	    continue
	if (i < co_argcount && match_args)
	    printf ("arg%d ", i + 1)
	python3_vars_seen[arg_name_str, co_filename_str] = co_name_str
	printf ("%s %s", arg_type_name_str, arg_name_str)
	printf (" in %s at %s", co_name_str, co_filename_str)
	p3_display_value (arg_value, " = ", " ")
	printf ("\n")
    }
}

probe python.function_entry = process("PYTHON3").mark ("function__entry")
{
    python3_frame = $arg1
}

probe python.function_return = process("PYTHON3").mark ("function__return")
{
    python3_frame = $arg1
}
