File: argument.py

package info (click to toggle)
pypy 5.6.0%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 97,040 kB
  • ctags: 185,069
  • sloc: python: 1,147,862; ansic: 49,642; cpp: 5,245; asm: 5,169; makefile: 529; sh: 481; xml: 232; lisp: 45
file content (254 lines) | stat: -rw-r--r-- 9,079 bytes parent folder | download | duplicates (8)
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