in library/_compiler.py [0:0]
def rewrite_str_mod(self, left, right): # noqa: C901
format_string = left.s
try:
if is_const(right):
return try_constant_fold_mod(format_string, right)
# Try and collapse the whole expression into a string
const_tuple = self.makeConstTuple(right.elts)
if const_tuple:
return ast.Str(format_string.__mod__(const_tuple.value))
except Exception:
pass
n_specifiers = 0
i = 0
length = len(format_string)
while i < length:
i = format_string.find("%", i)
if i == -1:
break
ch = format_string[i]
i += 1
if i >= length:
# Invalid format string ending in a single percent
return None
ch = format_string[i]
i += 1
if ch == "%":
# Break the string apart at '%'
continue
elif ch == "(":
# We don't support dict lookups and may get confused from
# inner '%' chars
return None
n_specifiers += 1
rhs = right
if isinstance(right, ast.Tuple):
rhs_values = rhs.elts
num_values = len(rhs_values)
else:
# If RHS is not a tuple constructor, then we only support the
# situation with a single format specifier in the string, by
# normalizing `rhs` to a one-element tuple:
# `_mod_check_single_arg(rhs)[0]`
rhs_values = None
if n_specifiers != 1:
return None
num_values = 1
i = 0
value_idx = 0
segment_begin = 0
strings = []
while i < length:
i = format_string.find("%", i)
if i == -1:
break
ch = format_string[i]
i += 1
segment_end = i - 1
if segment_end - segment_begin > 0:
substr = format_string[segment_begin:segment_end]
strings.append(ast.Str(substr))
if i >= length:
return None
ch = format_string[i]
i += 1
# Parse flags and width
spec_begin = i - 1
have_width = False
while True:
if ch == "0":
# TODO(matthiasb): Support ' ', '+', '#', etc
# They mostly have the same meaning. However they can
# appear in any order here but must follow stricter
# conventions in f-strings.
if i >= length:
return None
ch = format_string[i]
i += 1
continue
break
if "1" <= ch <= "9":
have_width = True
if i >= length:
return None
ch = format_string[i]
i += 1
while "0" <= ch <= "9":
if i >= length:
return None
ch = format_string[i]
i += 1
spec_str = ""
if i - 1 - spec_begin > 0:
spec_str = format_string[spec_begin : i - 1]
if ch == "%":
# Handle '%%'
segment_begin = i - 1
continue
# Handle remaining supported cases that use a value from RHS
if rhs_values is not None:
if value_idx >= num_values:
return None
value = rhs_values[value_idx]
else:
# We have a situation like `"%s" % x` without tuple on RHS.
# Transform to: f"{''._mod_check_single_arg(x)[0]}"
converted = create_conversion_call("_mod_check_single_arg", rhs)
value = ast.Subscript(converted, ast.Index(ast.Num(0)), ast.Load())
value_idx += 1
if ch in "sra":
# Rewrite "%s" % (x,) to f"{x!s}"
if have_width:
# Need to explicitly specify alignment because `%5s`
# aligns right, while `f"{x:5}"` aligns left.
spec_str = ">" + spec_str
format_spec = ast.Str(spec_str) if spec_str else None
formatted = ast.FormattedValue(value, ord(ch), format_spec)
strings.append(formatted)
elif ch in "diu":
# Rewrite "%d" % (x,) to f"{''._mod_convert_number_int(x)}".
# Calling a method on the empty string is a hack to access a
# well-known function regardless of the surrounding
# environment.
converted = create_conversion_call("_mod_convert_number_int", value)
format_spec = ast.Str(spec_str) if spec_str else None
formatted = ast.FormattedValue(converted, -1, format_spec)
strings.append(formatted)
elif ch in "xXo":
# Rewrite "%x" % (v,) to f"{''._mod_convert_number_index(v):x}".
# Calling a method on the empty string is a hack to access a
# well-known function regardless of the surrounding
# environment.
converted = create_conversion_call("_mod_convert_number_index", value)
format_spec = ast.Str(spec_str + ch)
formatted = ast.FormattedValue(converted, -1, format_spec)
strings.append(formatted)
else:
return None
# Begin next segment after specifier
segment_begin = i
if value_idx != num_values:
return None
segment_end = length
if segment_end - segment_begin > 0:
substr = format_string[segment_begin:segment_end]
strings.append(ast.Str(substr))
return ast.JoinedStr(strings)