More lvalue support for get_arg_name
- New get_line_indent function
- get_arg_value arg_num:
- Negative values correlate to lvalues
- 0 means return the function name
- Positive numbers behave as before
- sprint_varx: Specify str(key) instead of key to account for numeric keys
Change-Id: I541a9723ce48a8babe7652603f4dae18ba163df3
Signed-off-by: Michael Walsh <micwalsh@us.ibm.com>
diff --git a/lib/gen_print.py b/lib/gen_print.py
index 1b15997..d30ddb7 100755
--- a/lib/gen_print.py
+++ b/lib/gen_print.py
@@ -108,6 +108,14 @@
return func_name
+def get_line_indent(line):
+ r"""
+ Return the number of spaces at the beginning of the line.
+ """
+
+ return len(line) - len(line.lstrip(' '))
+
+
# get_arg_name is not a print function per se. I have included it in this
# module because it is used by sprint_var which is found in this module.
def get_arg_name(var,
@@ -119,26 +127,36 @@
Description of arguments:
var The variable whose name you want returned.
- arg_num The arg number (1 through n) whose name
- you wish to have returned. This value
- should not exceed the number of arguments
- allowed by the target function. If
- arg_num contains the special value 0,
- get_arg_name will return the lvalue, which
- is anything to the left of the assignment
- operator (i.e. "="). For example, if a
- programmer codes "var1 = my_function", and
- my_function calls this function with
- "get_arg_name(0, 0, stack_frame_ix=2)",
- this function will return "var1".
- Likewise, if a programmer codes "var1,
- var2 = my_function", and my_function calls
- this function with "get_arg_name(0, 0,
- stack_frame_ix=2)", this function will
- return "var1, var2". No manipulation of
- the lvalue string is done by this function
- (i.e. compressing spaces, converting to a
- list, etc.).
+ arg_num The arg number whose name is to be
+ returned. To illustrate how arg_num is
+ processed, suppose that a programmer codes
+ this line: "rc, outbuf = my_func(var1,
+ var2)" and suppose that my_func has this
+ line of code: "result = gp.get_arg_name(0,
+ arg_num, 2)". If arg_num is positive, the
+ indicated argument is returned. For
+ example, if arg_num is 1, "var1" would be
+ returned, If arg_num is 2, "var2" would be
+ returned. If arg_num exceeds the number
+ of arguments, get_arg_name will simply
+ return a complete list of the arguments.
+ If arg_num is 0, get_arg_name will return
+ the name of the target function as
+ specified in the calling line ("my_func"
+ in this case). To clarify, if the caller
+ of the target function uses an alias
+ function name, the alias name would be
+ returned. If arg_num is negative, an
+ lvalue variable name is returned.
+ Continuing with the given example, if
+ arg_num is -2 the 2nd parm to the left of
+ the "=" ("rc" in this case) should be
+ returned. If arg_num is -1, the 1st parm
+ to the left of the "=" ("out_buf" in this
+ case) should be returned. If arg_num is
+ less than -2, an entire dictionary is
+ returned. The keys to the dictionary for
+ this example would be -2 and -1.
stack_frame_ix The stack frame index of the target
function. This value must be 1 or
greater. 1 would indicate get_arg_name's
@@ -184,13 +202,6 @@
local_debug_show_source = int(
os.environ.get('GET_ARG_NAME_SHOW_SOURCE', 0))
- if arg_num < 0:
- print_error("Programmer error - Variable \"arg_num\" has an invalid" +
- " value of \"" + str(arg_num) + "\". The value must be" +
- " an integer that is 0 or greater.\n")
- # What is the best way to handle errors? Raise exception? I'll
- # revisit later.
- return
if stack_frame_ix < 1:
print_error("Programmer error - Variable \"stack_frame_ix\" has an" +
" invalid value of \"" + str(stack_frame_ix) + "\". The" +
@@ -232,7 +243,7 @@
print("Adjusted stack_frame_ix...")
print_varx("stack_frame_ix", stack_frame_ix, 0, debug_indent)
- called_func_name = sprint_func_name(stack_frame_ix)
+ real_called_func_name = sprint_func_name(stack_frame_ix)
module = inspect.getmodule(frame)
@@ -260,7 +271,8 @@
print_varx("line_ix", line_ix, 0, debug_indent)
if local_debug_show_source:
print_varx("source_lines", source_lines, 0, debug_indent)
- print_varx("called_func_name", called_func_name, 0, debug_indent)
+ print_varx("real_called_func_name", real_called_func_name, 0,
+ debug_indent)
# Get a list of all functions defined for the module. Note that this
# doesn't work consistently when _run_exitfuncs is at the top of the stack
@@ -272,16 +284,16 @@
# functions.
called_func_id = None
for func_name, function in all_functions:
- if func_name == called_func_name:
+ if func_name == real_called_func_name:
called_func_id = id(function)
break
# NOTE: The only time I've found that called_func_id can't be found is
# when we're running from an exit function.
# Look for other functions in module with matching id.
- aliases = set([called_func_name])
+ aliases = set([real_called_func_name])
for func_name, function in all_functions:
- if func_name == called_func_name:
+ if func_name == real_called_func_name:
continue
func_id = id(function)
if func_id == called_func_id:
@@ -292,9 +304,10 @@
# code to handle pvar, qpvar, dpvar, etc. aliases explicitly since they
# are defined in this module and used frequently.
# pvar is an alias for print_var.
- aliases.add(re.sub("print_var", "pvar", called_func_name))
+ aliases.add(re.sub("print_var", "pvar", real_called_func_name))
- func_regex = ".*(" + '|'.join(aliases) + ")[ ]*\("
+ func_name_regex = "(" + '|'.join(aliases) + ")"
+ pre_args_regex = ".*" + func_name_regex + "[ ]*\("
# Search backward through source lines looking for the calling function
# name.
@@ -303,43 +316,65 @@
# Skip comment lines.
if re.match(r"[ ]*#", source_lines[start_line_ix]):
continue
- if re.match(func_regex, source_lines[start_line_ix]):
+ if re.match(pre_args_regex, source_lines[start_line_ix]):
found = True
break
if not found:
print_error("Programmer error - Could not find the source line with" +
- " a reference to function \"" + called_func_name + "\".\n")
+ " a reference to function \"" + real_called_func_name +
+ "\".\n")
return
# Search forward through the source lines looking for a line whose
# indentation is the same or less than the start line. The end of our
# composite line should be the line preceding that line.
- start_indent = len(source_lines[start_line_ix]) -\
- len(source_lines[start_line_ix].lstrip(' '))
- end_line_ix = line_ix
+ start_indent = get_line_indent(source_lines[start_line_ix])
for end_line_ix in range(line_ix + 1, len(source_lines)):
if source_lines[end_line_ix].strip() == "":
continue
- line_indent = len(source_lines[end_line_ix]) -\
- len(source_lines[end_line_ix].lstrip(' '))
+ line_indent = get_line_indent(source_lines[end_line_ix])
if line_indent <= start_indent:
end_line_ix -= 1
break
+ if start_line_ix != 0:
+ # Check to see whether the start line is a continuation of the prior
+ # line?
+ line_indent = get_line_indent(source_lines[start_line_ix - 1])
+ if line_indent < start_indent:
+ start_line_ix -= 1
+ # Remove the backslash (continuation char).
+ source_lines[start_line_ix] = re.sub(r"[ ]*\\([\r\n]$)",
+ " \\1",
+ source_lines[start_line_ix])
# Join the start line through the end line into a composite line.
composite_line = ''.join(map(str.strip,
source_lines[start_line_ix:end_line_ix + 1]))
+ # Insert one space after first "=" if there isn't one already.
+ composite_line = re.sub("=[ ]*([^ ])", "= \\1", composite_line, 1)
- # arg_list_etc = re.sub(".*" + called_func_name, "", composite_line)
- arg_list_etc = "(" + re.sub(func_regex, "", composite_line)
- lvalue = re.sub("[ ]+=[ ]+" + called_func_name + ".*", "", composite_line)
+ lvalue_regex = "[ ]+=[ ]*" + func_name_regex + ".*"
+ lvalue_string = re.sub(lvalue_regex, "", composite_line)
+ lvalues_list = map(str.strip, lvalue_string.split(","))
+ lvalues = collections.OrderedDict()
+ ix = len(lvalues_list) * -1
+ for lvalue in lvalues_list:
+ lvalues[ix] = lvalue
+ ix += 1
+ called_func_name = re.sub("(.*=)?[ ]+" + func_name_regex +
+ "[ ]*\(.*", "\\2",
+ composite_line)
+ arg_list_etc = "(" + re.sub(pre_args_regex, "", composite_line)
if local_debug:
print_varx("aliases", aliases, 0, debug_indent)
- print_varx("func_regex", func_regex, 0, debug_indent)
+ print_varx("pre_args_regex", pre_args_regex, 0, debug_indent)
print_varx("start_line_ix", start_line_ix, 0, debug_indent)
print_varx("end_line_ix", end_line_ix, 0, debug_indent)
print_varx("composite_line", composite_line, 0, debug_indent)
- print_varx("lvalue", lvalue, 0, debug_indent)
+ print_varx("lvalue_regex", lvalue_regex, 0, debug_indent)
+ print_varx("lvalue_string", lvalue_string, 0, debug_indent)
+ print_varx("lvalues", lvalues, 0, debug_indent)
+ print_varx("called_func_name", called_func_name, 0, debug_indent)
print_varx("arg_list_etc", arg_list_etc, 0, debug_indent)
# Parse arg list...
@@ -373,17 +408,18 @@
# Trim whitespace from each list entry.
args_list = [arg.strip() for arg in args_list]
- if arg_num > len(args_list):
- print_error("Programmer error - The caller has asked for the name of" +
- " argument number \"" + str(arg_num) + "\" but there " +
- "were only \"" + str(len(args_list)) + "\" args used:\n" +
- sprint_varx("args_list", args_list))
- return
-
- if arg_num == 0:
- argument = lvalue
+ if arg_num < 0:
+ if abs(arg_num) > len(lvalues):
+ argument = lvalues
+ else:
+ argument = lvalues[arg_num]
+ elif arg_num == 0:
+ argument = called_func_name
else:
- argument = args_list[arg_num - 1]
+ if arg_num > len(args_list):
+ argument = args_list
+ else:
+ argument = args_list[arg_num - 1]
if local_debug:
print_varx("args_list", args_list, 0, debug_indent)
@@ -680,9 +716,10 @@
loc_trailing_char,
key_list)
else:
- buffer += sprint_varx(var_name + "[" + key + "]", value,
- hex, loc_col1_indent, loc_col1_width,
- loc_trailing_char, key_list)
+ buffer += sprint_varx(var_name + "[" + str(key) + "]",
+ value, hex, loc_col1_indent,
+ loc_col1_width, loc_trailing_char,
+ key_list)
elif type(var_value) in (list, tuple, set):
for key, value in enumerate(var_value):
ix += 1