1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
|
"""
Arguments objects.
"""
from rpython.annotator.model import SomeTuple
from rpython.flowspace.argument import CallSpec
class ArgumentsForTranslation(CallSpec):
@property
def positional_args(self):
if self.w_stararg is not None:
args_w = self.unpackiterable(self.w_stararg)
return self.arguments_w + args_w
else:
return self.arguments_w
def newtuple(self, items_s):
return SomeTuple(items_s)
def unpackiterable(self, s_obj):
assert isinstance(s_obj, SomeTuple)
return list(s_obj.items)
def fixedunpack(self, argcount):
"""The simplest argument parsing: get the 'argcount' arguments,
or raise a real ValueError if the length is wrong."""
if self.keywords:
raise ValueError("no keyword arguments expected")
if len(self.arguments_w) > argcount:
raise ValueError("too many arguments (%d expected)" % argcount)
elif len(self.arguments_w) < argcount:
raise ValueError("not enough arguments (%d expected)" % argcount)
return self.arguments_w
def prepend(self, w_firstarg): # used often
"Return a new Arguments with a new argument inserted first."
return ArgumentsForTranslation([w_firstarg] + self.arguments_w,
self.keywords, self.w_stararg)
def copy(self):
return ArgumentsForTranslation(self.arguments_w, self.keywords,
self.w_stararg)
def _match_signature(self, scope_w, signature, defaults_w=None):
"""Parse args and kwargs according to the signature of a code object,
or raise an ArgErr in case of failure.
"""
# args_w = list of the normal actual parameters, wrapped
# scope_w = resulting list of wrapped values
#
co_argcount = signature.num_argnames() # expected formal arguments, without */**
args_w = self.positional_args
num_args = len(args_w)
keywords = self.keywords
num_kwds = len(keywords)
# put as many positional input arguments into place as available
take = min(num_args, co_argcount)
scope_w[:take] = args_w[:take]
input_argcount = take
# collect extra positional arguments into the *vararg
if signature.has_vararg():
if num_args > co_argcount:
starargs_w = args_w[co_argcount:]
else:
starargs_w = []
scope_w[co_argcount] = self.newtuple(starargs_w)
elif num_args > co_argcount:
raise ArgErrCount(num_args, num_kwds, signature, defaults_w, 0)
assert not signature.has_kwarg() # XXX should not happen?
# handle keyword arguments
num_remainingkwds = 0
kwds_mapping = None
if num_kwds:
# kwds_mapping maps target indexes in the scope (minus input_argcount)
# to keyword names
kwds_mapping = []
# match the keywords given at the call site to the argument names
# the called function takes
# this function must not take a scope_w, to make the scope not
# escape
num_remainingkwds = len(keywords)
for name in keywords:
j = signature.find_argname(name)
# if j == -1 nothing happens
if j < input_argcount:
# check that no keyword argument conflicts with these.
if j >= 0:
raise ArgErrMultipleValues(name)
else:
kwds_mapping.append(name)
num_remainingkwds -= 1
if num_remainingkwds:
if co_argcount == 0:
raise ArgErrCount(num_args, num_kwds, signature, defaults_w, 0)
raise ArgErrUnknownKwds(num_remainingkwds, keywords,
kwds_mapping)
# check for missing arguments and fill them from the kwds,
# or with defaults, if available
missing = 0
if input_argcount < co_argcount:
def_first = co_argcount - (0 if defaults_w is None else len(defaults_w))
j = 0
for i in range(input_argcount, co_argcount):
name = signature.argnames[i]
if name in keywords:
scope_w[i] = keywords[name]
continue
defnum = i - def_first
if defnum >= 0:
scope_w[i] = defaults_w[defnum]
else:
missing += 1
if missing:
raise ArgErrCount(num_args, num_kwds, signature, defaults_w, missing)
def unpack(self):
"Return a ([w1,w2...], {'kw':w3...}) pair."
return self.positional_args, self.keywords
def match_signature(self, signature, defaults_w):
"""Parse args and kwargs according to the signature of a code object,
or raise an ArgErr in case of failure.
"""
scopelen = signature.scope_length()
scope_w = [None] * scopelen
self._match_signature(scope_w, signature, defaults_w)
return scope_w
def unmatch_signature(self, signature, data_w):
"""kind of inverse of match_signature"""
argnames, varargname, kwargname = signature
assert kwargname is None
cnt = len(argnames)
need_cnt = len(self.positional_args)
if varargname:
assert len(data_w) == cnt + 1
stararg_w = self.unpackiterable(data_w[cnt])
if stararg_w:
args_w = data_w[:cnt] + stararg_w
assert len(args_w) == need_cnt
assert not self.keywords
return ArgumentsForTranslation(args_w, {})
else:
data_w = data_w[:-1]
assert len(data_w) == cnt
assert len(data_w) >= need_cnt
args_w = data_w[:need_cnt]
_kwds_w = dict(zip(argnames[need_cnt:], data_w[need_cnt:]))
keywords_w = [_kwds_w[key] for key in self.keywords]
return ArgumentsForTranslation(args_w, dict(zip(self.keywords, keywords_w)))
def rawshape(args):
return args._rawshape()
def simple_args(args_s):
return ArgumentsForTranslation(list(args_s))
def complex_args(args_s):
return ArgumentsForTranslation.fromshape(args_s[0].const,
list(args_s[1:]))
#
# ArgErr family of exceptions raised in case of argument mismatch.
# We try to give error messages following CPython's, which are very informative.
#
class ArgErr(Exception):
def getmsg(self):
raise NotImplementedError
class ArgErrCount(ArgErr):
def __init__(self, got_nargs, nkwds, signature,
defaults_w, missing_args):
self.signature = signature
self.num_defaults = 0 if defaults_w is None else len(defaults_w)
self.missing_args = missing_args
self.num_args = got_nargs
self.num_kwds = nkwds
def getmsg(self):
n = self.signature.num_argnames()
if n == 0:
msg = "takes no arguments (%d given)" % (
self.num_args + self.num_kwds)
else:
defcount = self.num_defaults
has_kwarg = self.signature.has_kwarg()
num_args = self.num_args
num_kwds = self.num_kwds
if defcount == 0 and not self.signature.has_vararg():
msg1 = "exactly"
if not has_kwarg:
num_args += num_kwds
num_kwds = 0
elif not self.missing_args:
msg1 = "at most"
else:
msg1 = "at least"
has_kwarg = False
n -= defcount
if n == 1:
plural = ""
else:
plural = "s"
if has_kwarg or num_kwds > 0:
msg2 = " non-keyword"
else:
msg2 = ""
msg = "takes %s %d%s argument%s (%d given)" % (
msg1,
n,
msg2,
plural,
num_args)
return msg
class ArgErrMultipleValues(ArgErr):
def __init__(self, argname):
self.argname = argname
def getmsg(self):
msg = "got multiple values for keyword argument '%s'" % (
self.argname)
return msg
class ArgErrUnknownKwds(ArgErr):
def __init__(self, num_remainingkwds, keywords, kwds_mapping):
name = ''
self.num_kwds = num_remainingkwds
if num_remainingkwds == 1:
for name in keywords:
if name not in kwds_mapping:
break
self.kwd_name = name
def getmsg(self):
if self.num_kwds == 1:
msg = "got an unexpected keyword argument '%s'" % (
self.kwd_name)
else:
msg = "got %d unexpected keyword arguments" % (
self.num_kwds)
return msg
|