1"""
2C wrapper wrapper
3"""
4from typehandlers.base import TypeConfigurationError, CodeGenerationError, NotSupportedError
5from typehandlers.base import ForwardWrapperBase
6from typehandlers.codesink import NullCodeSink
7import utils
8import settings
9import traceback
10import sys
11
12try:
13    set
14except NameError:
15    from sets import Set as set   # Python 2.3 fallback
16
17
18def isiterable(obj):
19    """Returns True if an object appears to be iterable"""
20    return hasattr(obj, '__iter__') or isinstance(obj, basestring)
21
22def vector_counter(vec):
23    """
24    >>> list(vector_counter([[1,2], ['a', 'b'], ['x', 'y']]))
25    [[1, 'a', 'x'], [1, 'a', 'y'], [1, 'b', 'x'], [1, 'b', 'y'], [2, 'a', 'x'], [2, 'a', 'y'], [2, 'b', 'x'], [2, 'b', 'y']]
26    """
27    iters = [iter(l) for l in vec]
28    values = [it.next() for it in iters[:-1]] + [vec[-1][0]]
29    while 1:
30        for idx in xrange(len(iters)-1, -1, -1):
31            try:
32                values[idx] = iters[idx].next()
33            except StopIteration:
34                iters[idx] = iter(vec[idx])
35                values[idx] = iters[idx].next()
36            else:
37                break
38        else:
39            raise StopIteration
40        yield list(values)
41
42class OverloadedWrapper(object):
43    """
44    An object that aggregates a set of wrapper objects; it generates
45    a single python wrapper wrapper that supports overloading,
46    i.e. tries to parse parameters according to each individual
47    Function parameter list, and uses the first wrapper that doesn't
48    generate parameter parsing error.
49    """
50
51    RETURN_TYPE = NotImplemented
52    ERROR_RETURN = NotImplemented
53
54    def __init__(self, wrapper_name):
55        """
56        wrapper_name -- C/C++ name of the wrapper
57        """
58        self.wrappers = []
59        self.all_wrappers = None
60        self.wrapper_name = wrapper_name
61        self.wrapper_actual_name = None
62        self.wrapper_return = None
63        self.wrapper_args = None
64        self.pystruct = 'PyObject'
65        #self.static_decl = True ## FIXME: unused?
66#         self.enable_implicit_conversions = True
67
68    def add(self, wrapper):
69        """
70        Add a wrapper to the overloaded wrapper
71        wrapper -- a Wrapper object
72        """
73        assert isinstance(wrapper, ForwardWrapperBase)
74        self.wrappers.append(wrapper)
75        return wrapper
76
77    def _normalize_py_method_flags(self):
78        """
79        Checks that if all overloaded wrappers have similar method
80        flags, forcing similar flags if needed (via method.force_parse
81        = ForwardWrapperBase.PARSE_TUPLE_AND_KEYWORDS)
82        """
83
84        if len(self.wrappers) == 1:
85            return
86
87        for wrapper in self.wrappers:
88            wrapper.force_parse = ForwardWrapperBase.PARSE_TUPLE_AND_KEYWORDS
89
90        # loop that keeps removing wrappers until all remaining wrappers have the same flags
91        modified = True
92        while modified:
93            existing_flags = None
94            modified = False
95            for wrapper in self.wrappers:
96                try:
97                    wrapper_flags = utils.call_with_error_handling(
98                        wrapper.get_py_method_def_flags, args=(), kwargs={}, wrapper=wrapper)
99                except utils.SkipWrapper, ex:
100                    modified = True
101                    self.wrappers.remove(wrapper)
102                    dummy1, dummy2, tb = sys.exc_info()
103                    settings.error_handler.handle_error(wrapper, ex, tb)
104                    break
105
106                wrapper_flags = set(wrapper_flags)
107                if existing_flags is None:
108                    existing_flags = wrapper_flags
109                else:
110                    if wrapper_flags != existing_flags:
111                        modified = True
112                        self.wrappers.remove(wrapper)
113                        tb = traceback.extract_stack()
114                        settings.error_handler.handle_error(wrapper, ex, tb)
115                        break
116
117    def _compute_all_wrappers(self):
118        """
119        Computes all the wrappers that should be generated; this
120        includes not only the regular overloaded wrappers but also
121        additional wrappers created in runtime to fulfil implicit
122        conversion requirements.  The resulting list is stored as
123        self.all_wrappers
124        """
125        self.all_wrappers = list(self.wrappers)
126
127    def generate(self, code_sink):
128        """
129        Generate all the wrappers plus the 'aggregator' wrapper to a code sink.
130        """
131        self._normalize_py_method_flags()
132        self._compute_all_wrappers()
133        if len(self.all_wrappers) == 0:
134            raise utils.SkipWrapper
135        elif len(self.all_wrappers) == 1 \
136                and not getattr(self.all_wrappers[0], 'NEEDS_OVERLOADING_INTERFACE', False):
137            ## special case when there's only one wrapper; keep
138            ## simple things simple
139
140            #self.all_wrappers[0].generate(code_sink)
141            prototype_line = utils.call_with_error_handling(self.all_wrappers[0].generate,
142                                                            (code_sink,), {}, self.all_wrappers[0])
143
144            self.wrapper_actual_name = self.all_wrappers[0].wrapper_actual_name
145            assert self.wrapper_actual_name is not None
146            self.wrapper_return = self.all_wrappers[0].wrapper_return
147            self.wrapper_args = self.all_wrappers[0].wrapper_args
148        else:
149            ## multiple overloaded wrappers case..
150            flags = self.all_wrappers[0].get_py_method_def_flags()
151
152            ## Generate the individual "low level" wrappers that handle a single prototype
153            self.wrapper_actual_name = self.all_wrappers[0].wrapper_base_name
154            delegate_wrappers = []
155            for number, wrapper in enumerate(self.all_wrappers):
156                ## enforce uniform method flags
157                wrapper.force_parse = wrapper.PARSE_TUPLE_AND_KEYWORDS
158                ## an extra parameter 'return_exception' is used to
159                ## return parse error exceptions to the 'main wrapper'
160                error_return = """{
161    PyObject *exc_type, *traceback;
162    PyErr_Fetch(&exc_type, return_exception, &traceback);
163    Py_XDECREF(exc_type);
164    Py_XDECREF(traceback);
165}
166%s""" % (self.ERROR_RETURN,)
167                wrapper_name = "%s__%i" % (self.wrapper_actual_name, number)
168                wrapper.set_parse_error_return(error_return)
169                code_sink.writeln()
170
171                # wrapper.generate(code_sink, wrapper_name,
172                #                  extra_wrapper_params=["PyObject **return_exception"])
173                try:
174                    utils.call_with_error_handling(
175                        wrapper.generate, args=(code_sink, wrapper_name),
176                        kwargs=dict(extra_wrapper_params=["PyObject **return_exception"]),
177                        wrapper=wrapper)
178                except utils.SkipWrapper:
179                    continue
180
181                delegate_wrappers.append(wrapper.wrapper_actual_name)
182
183            ## if all wrappers did not generate, then the overload
184            ## aggregator wrapper should not be generated either..
185            if not delegate_wrappers:
186                raise utils.SkipWrapper
187
188            ## Generate the 'main wrapper' that calls the other ones
189            code_sink.writeln()
190            self.wrapper_return = self.RETURN_TYPE
191            self.wrapper_args = ['%s *self' % self.pystruct]
192            if 'METH_VARARGS' in flags:
193                self.wrapper_args.append('PyObject *args')
194            if 'METH_KEYWORDS' in flags:
195                self.wrapper_args.append('PyObject *kwargs')
196            prototype_line = "%s %s(%s)" % (self.wrapper_return, self.wrapper_actual_name, ', '.join(self.wrapper_args))
197            code_sink.writeln(prototype_line)
198            code_sink.writeln('{')
199            code_sink.indent()
200            code_sink.writeln(self.RETURN_TYPE + ' retval;')
201            code_sink.writeln('PyObject *error_list;')
202            code_sink.writeln('PyObject *exceptions[%i] = {0,};' % len(delegate_wrappers))
203            for number, delegate_wrapper in enumerate(delegate_wrappers):
204                ## call the delegate wrapper
205                args = ['self']
206                if 'METH_VARARGS' in flags:
207                    args.append('args')
208                if 'METH_KEYWORDS' in flags:
209                    args.append('kwargs')
210                args.append('&exceptions[%i]' % number)
211                code_sink.writeln("retval = %s(%s);" % (delegate_wrapper, ', '.join(args)))
212                ## if no parse exception, call was successful:
213                ## free previous exceptions and return the result
214                code_sink.writeln("if (!exceptions[%i]) {" % number)
215                code_sink.indent()
216                for i in xrange(number):
217                    code_sink.writeln("Py_DECREF(exceptions[%i]);" % i)
218                code_sink.writeln("return retval;")
219                code_sink.unindent()
220                code_sink.writeln("}")
221
222            ## If the following generated code is reached it means
223            ## that all of our delegate wrappers had parsing errors:
224            ## raise an appropriate exception, free the previous
225            ## exceptions, and return NULL
226            code_sink.writeln('error_list = PyList_New(%i);' % len(delegate_wrappers))
227            for i in xrange(len(delegate_wrappers)):
228                code_sink.writeln(
229                    'PyList_SET_ITEM(error_list, %i, PyObject_Str(exceptions[%i]));'
230                    % (i, i))
231                code_sink.writeln("Py_DECREF(exceptions[%i]);" % i)
232            code_sink.writeln('PyErr_SetObject(PyExc_TypeError, error_list);')
233            code_sink.writeln("Py_DECREF(error_list);")
234            code_sink.writeln(self.ERROR_RETURN)
235            code_sink.unindent()
236            code_sink.writeln('}')
237
238        return prototype_line
239
240    def get_py_method_def(self, name):
241        """
242        Returns an array element to use in a PyMethodDef table.
243        Should only be called after code generation.
244
245        name -- python wrapper/method name
246        """
247        if len(self.all_wrappers) == 1 \
248                and not getattr(self.all_wrappers[0], 'NEEDS_OVERLOADING_INTERFACE', False):
249            return self.all_wrappers[0].get_py_method_def(name)
250        else:
251            self._normalize_py_method_flags()
252            flags = self.all_wrappers[0].get_py_method_def_flags()
253            ## detect inconsistencies in flags; they must all be the same
254            if __debug__:
255                for func in self.all_wrappers:
256                    try:
257                        assert set(func.get_py_method_def_flags()) == set(flags),\
258                            ("Expected PyMethodDef flags %r, got %r"
259                             % (flags, func.get_py_method_def_flags()))
260                    except (TypeConfigurationError,
261                            CodeGenerationError,
262                            NotSupportedError):
263                        pass
264            docstring = None # FIXME
265
266            assert isinstance(self.wrapper_return, basestring)
267            assert isinstance(self.wrapper_actual_name, basestring)
268            assert isinstance(self.wrapper_args, list)
269
270            return "{(char *) \"%s\", (PyCFunction) %s, %s, %s }," % \
271                (name, self.wrapper_actual_name, '|'.join(flags),
272                 (docstring is None and "NULL" or ('"'+docstring+'"')))
273
274    def generate_declaration(self, code_sink):
275        self.reset_code_generation_state()
276        self._compute_all_wrappers()
277        self.generate(NullCodeSink())
278        assert isinstance(self.wrapper_return, basestring)
279        assert isinstance(self.wrapper_actual_name, basestring)
280        assert isinstance(self.wrapper_args, list)
281        code_sink.writeln("%s %s(%s);" % (self.wrapper_return, self.wrapper_actual_name, ', '.join(self.wrapper_args)))
282        self.reset_code_generation_state()
283
284    def generate_class_declaration(self, code_sink):
285        self.reset_code_generation_state()
286        self._compute_all_wrappers()
287        self.generate(NullCodeSink())
288        assert isinstance(self.wrapper_return, basestring)
289        assert isinstance(self.wrapper_actual_name, basestring)
290        assert isinstance(self.wrapper_args, list)
291        name = self.wrapper_actual_name.split('::')[-1]
292        code_sink.writeln("static %s %s(%s);" % (self.wrapper_return, name, ', '.join(self.wrapper_args)))
293
294        if len(self.all_wrappers) > 1:
295            for wrapper in self.all_wrappers:
296                name = wrapper.wrapper_actual_name.split('::')[-1]
297                code_sink.writeln("static %s %s(%s);" % (wrapper.wrapper_return, name, ', '.join(wrapper.wrapper_args)))
298
299        self.reset_code_generation_state()
300
301    def reset_code_generation_state(self):
302        self._compute_all_wrappers()
303        for wrapper in self.all_wrappers:
304            wrapper.reset_code_generation_state()
305
306    def get_section(self):
307        section = None
308        if self.all_wrappers is None:
309            self._compute_all_wrappers()
310        for wrapper in self.all_wrappers:
311            if section is None:
312                section = wrapper.section
313        return section
314
315    section = property(get_section)
316
317
318from cppclass import CppClassParameter, CppClassRefParameter
319
320