1"""
2Serializes a Cython code tree to Cython code. This is primarily useful for
3debugging and testing purposes.
4
5The output is in a strict format, no whitespace or comments from the input
6is preserved (and it could not be as it is not present in the code tree).
7"""
8
9from Cython.Compiler.Visitor import TreeVisitor
10from Cython.Compiler.ExprNodes import *
11
12class LinesResult(object):
13    def __init__(self):
14        self.lines = []
15        self.s = u""
16
17    def put(self, s):
18        self.s += s
19
20    def newline(self):
21        self.lines.append(self.s)
22        self.s = u""
23
24    def putline(self, s):
25        self.put(s)
26        self.newline()
27
28class DeclarationWriter(TreeVisitor):
29
30    indent_string = u"    "
31
32    def __init__(self, result = None):
33        super(DeclarationWriter, self).__init__()
34        if result is None:
35            result = LinesResult()
36        self.result = result
37        self.numindents = 0
38        self.tempnames = {}
39        self.tempblockindex = 0
40
41    def write(self, tree):
42        self.visit(tree)
43        return self.result
44
45    def indent(self):
46        self.numindents += 1
47
48    def dedent(self):
49        self.numindents -= 1
50
51    def startline(self, s = u""):
52        self.result.put(self.indent_string * self.numindents + s)
53
54    def put(self, s):
55        self.result.put(s)
56
57    def putline(self, s):
58        self.result.putline(self.indent_string * self.numindents + s)
59
60    def endline(self, s = u""):
61        self.result.putline(s)
62
63    def line(self, s):
64        self.startline(s)
65        self.endline()
66
67    def comma_separated_list(self, items, output_rhs=False):
68        if len(items) > 0:
69            for item in items[:-1]:
70                self.visit(item)
71                if output_rhs and item.default is not None:
72                    self.put(u" = ")
73                    self.visit(item.default)
74                self.put(u", ")
75            self.visit(items[-1])
76
77    def visit_Node(self, node):
78        raise AssertionError("Node not handled by serializer: %r" % node)
79
80    def visit_ModuleNode(self, node):
81        self.visitchildren(node)
82
83    def visit_StatListNode(self, node):
84        self.visitchildren(node)
85
86    def visit_CDefExternNode(self, node):
87        if node.include_file is None:
88            file = u'*'
89        else:
90            file = u'"%s"' % node.include_file
91        self.putline(u"cdef extern from %s:" % file)
92        self.indent()
93        self.visit(node.body)
94        self.dedent()
95
96    def visit_CPtrDeclaratorNode(self, node):
97        self.put('*')
98        self.visit(node.base)
99
100    def visit_CReferenceDeclaratorNode(self, node):
101        self.put('&')
102        self.visit(node.base)
103
104    def visit_CArrayDeclaratorNode(self, node):
105        self.visit(node.base)
106        self.put(u'[')
107        if node.dimension is not None:
108            self.visit(node.dimension)
109        self.put(u']')
110
111    def visit_CArrayDeclaratorNode(self, node):
112        self.visit(node.base)
113        self.put(u'[')
114        if node.dimension is not None:
115            self.visit(node.dimension)
116        self.put(u']')
117
118    def visit_CFuncDeclaratorNode(self, node):
119        # TODO: except, gil, etc.
120        self.visit(node.base)
121        self.put(u'(')
122        self.comma_separated_list(node.args)
123        self.endline(u')')
124
125    def visit_CNameDeclaratorNode(self, node):
126        self.put(node.name)
127
128    def visit_CSimpleBaseTypeNode(self, node):
129        # See Parsing.p_sign_and_longness
130        if node.is_basic_c_type:
131            self.put(("unsigned ", "", "signed ")[node.signed])
132            if node.longness < 0:
133                self.put("short " * -node.longness)
134            elif node.longness > 0:
135                self.put("long " * node.longness)
136        self.put(node.name)
137
138    def visit_CComplexBaseTypeNode(self, node):
139        self.put(u'(')
140        self.visit(node.base_type)
141        self.visit(node.declarator)
142        self.put(u')')
143
144    def visit_CNestedBaseTypeNode(self, node):
145        self.visit(node.base_type)
146        self.put(u'.')
147        self.put(node.name)
148
149    def visit_TemplatedTypeNode(self, node):
150        self.visit(node.base_type_node)
151        self.put(u'[')
152        self.comma_separated_list(node.positional_args + node.keyword_args.key_value_pairs)
153        self.put(u']')
154
155    def visit_CVarDefNode(self, node):
156        self.startline(u"cdef ")
157        self.visit(node.base_type)
158        self.put(u" ")
159        self.comma_separated_list(node.declarators, output_rhs=True)
160        self.endline()
161
162    def visit_container_node(self, node, decl, extras, attributes):
163        # TODO: visibility
164        self.startline(decl)
165        if node.name:
166            self.put(u' ')
167            self.put(node.name)
168            if node.cname is not None:
169                self.put(u' "%s"' % node.cname)
170        if extras:
171            self.put(extras)
172        self.endline(':')
173        self.indent()
174        if not attributes:
175            self.putline('pass')
176        else:
177            for attribute in attributes:
178                self.visit(attribute)
179        self.dedent()
180
181    def visit_CStructOrUnionDefNode(self, node):
182        if node.typedef_flag:
183            decl = u'ctypedef '
184        else:
185            decl = u'cdef '
186        if node.visibility == 'public':
187            decl += u'public '
188        if node.packed:
189            decl += u'packed '
190        decl += node.kind
191        self.visit_container_node(node, decl, None, node.attributes)
192
193    def visit_CppClassNode(self, node):
194        extras = ""
195        if node.templates:
196            extras = u"[%s]" % ", ".join(node.templates)
197        if node.base_classes:
198            extras += "(%s)" % ", ".join(node.base_classes)
199        self.visit_container_node(node, u"cdef cppclass", extras, node.attributes)
200
201    def visit_CEnumDefNode(self, node):
202        self.visit_container_node(node, u"cdef enum", None, node.items)
203
204    def visit_CEnumDefItemNode(self, node):
205        self.startline(node.name)
206        if node.cname:
207            self.put(u' "%s"' % node.cname)
208        if node.value:
209            self.put(u" = ")
210            self.visit(node.value)
211        self.endline()
212
213    def visit_CClassDefNode(self, node):
214        assert not node.module_name
215        if node.decorators:
216            for decorator in node.decorators:
217                self.visit(decorator)
218        self.startline(u"cdef class ")
219        self.put(node.class_name)
220        if node.base_class_name:
221            self.put(u"(")
222            if node.base_class_module:
223                self.put(node.base_class_module)
224                self.put(u".")
225            self.put(node.base_class_name)
226            self.put(u")")
227        self.endline(u":")
228        self.indent()
229        self.visit(node.body)
230        self.dedent()
231
232    def visit_CTypeDefNode(self, node):
233        self.startline(u"ctypedef ")
234        self.visit(node.base_type)
235        self.put(u" ")
236        self.visit(node.declarator)
237        self.endline()
238
239    def visit_FuncDefNode(self, node):
240        self.startline(u"def %s(" % node.name)
241        self.comma_separated_list(node.args)
242        self.endline(u"):")
243        self.indent()
244        self.visit(node.body)
245        self.dedent()
246
247    def visit_CArgDeclNode(self, node):
248        if node.base_type.name is not None:
249            self.visit(node.base_type)
250            self.put(u" ")
251        self.visit(node.declarator)
252        if node.default is not None:
253            self.put(u" = ")
254            self.visit(node.default)
255
256    def visit_CImportStatNode(self, node):
257        self.startline(u"cimport ")
258        self.put(node.module_name)
259        if node.as_name:
260            self.put(u" as ")
261            self.put(node.as_name)
262        self.endline()
263
264    def visit_FromCImportStatNode(self, node):
265        self.startline(u"from ")
266        self.put(node.module_name)
267        self.put(u" cimport ")
268        first = True
269        for pos, name, as_name, kind in node.imported_names:
270            assert kind is None
271            if first:
272                first = False
273            else:
274                self.put(u", ")
275            self.put(name)
276            if as_name:
277                self.put(u" as ")
278                self.put(as_name)
279        self.endline()
280
281    def visit_NameNode(self, node):
282        self.put(node.name)
283
284    def visit_IntNode(self, node):
285        self.put(node.value)
286
287    def visit_NoneNode(self, node):
288        self.put(u"None")
289
290    def visit_NotNode(self, node):
291        self.put(u"(not ")
292        self.visit(node.operand)
293        self.put(u")")
294
295    def visit_DecoratorNode(self, node):
296        self.startline("@")
297        self.visit(node.decorator)
298        self.endline()
299
300    def visit_BinopNode(self, node):
301        self.visit(node.operand1)
302        self.put(u" %s " % node.operator)
303        self.visit(node.operand2)
304
305    def visit_AttributeNode(self, node):
306        self.visit(node.obj)
307        self.put(u".%s" % node.attribute)
308
309    def visit_BoolNode(self, node):
310        self.put(str(node.value))
311
312    # FIXME: represent string nodes correctly
313    def visit_StringNode(self, node):
314        value = node.value
315        if value.encoding is not None:
316            value = value.encode(value.encoding)
317        self.put(repr(value))
318
319    def visit_PassStatNode(self, node):
320        self.startline(u"pass")
321        self.endline()
322
323class CodeWriter(DeclarationWriter):
324
325    def visit_SingleAssignmentNode(self, node):
326        self.startline()
327        self.visit(node.lhs)
328        self.put(u" = ")
329        self.visit(node.rhs)
330        self.endline()
331
332    def visit_CascadedAssignmentNode(self, node):
333        self.startline()
334        for lhs in node.lhs_list:
335            self.visit(lhs)
336            self.put(u" = ")
337        self.visit(node.rhs)
338        self.endline()
339
340    def visit_PrintStatNode(self, node):
341        self.startline(u"print ")
342        self.comma_separated_list(node.arg_tuple.args)
343        if not node.append_newline:
344            self.put(u",")
345        self.endline()
346
347    def visit_ForInStatNode(self, node):
348        self.startline(u"for ")
349        self.visit(node.target)
350        self.put(u" in ")
351        self.visit(node.iterator.sequence)
352        self.endline(u":")
353        self.indent()
354        self.visit(node.body)
355        self.dedent()
356        if node.else_clause is not None:
357            self.line(u"else:")
358            self.indent()
359            self.visit(node.else_clause)
360            self.dedent()
361
362    def visit_IfStatNode(self, node):
363        # The IfClauseNode is handled directly without a seperate match
364        # for clariy.
365        self.startline(u"if ")
366        self.visit(node.if_clauses[0].condition)
367        self.endline(":")
368        self.indent()
369        self.visit(node.if_clauses[0].body)
370        self.dedent()
371        for clause in node.if_clauses[1:]:
372            self.startline("elif ")
373            self.visit(clause.condition)
374            self.endline(":")
375            self.indent()
376            self.visit(clause.body)
377            self.dedent()
378        if node.else_clause is not None:
379            self.line("else:")
380            self.indent()
381            self.visit(node.else_clause)
382            self.dedent()
383
384    def visit_SequenceNode(self, node):
385        self.comma_separated_list(node.args) # Might need to discover whether we need () around tuples...hmm...
386
387    def visit_SimpleCallNode(self, node):
388        self.visit(node.function)
389        self.put(u"(")
390        self.comma_separated_list(node.args)
391        self.put(")")
392
393    def visit_GeneralCallNode(self, node):
394        self.visit(node.function)
395        self.put(u"(")
396        posarg = node.positional_args
397        if isinstance(posarg, AsTupleNode):
398            self.visit(posarg.arg)
399        else:
400            self.comma_separated_list(posarg)
401        if node.keyword_args is not None or node.starstar_arg is not None:
402            raise Exception("Not implemented yet")
403        self.put(u")")
404
405    def visit_ExprStatNode(self, node):
406        self.startline()
407        self.visit(node.expr)
408        self.endline()
409
410    def visit_InPlaceAssignmentNode(self, node):
411        self.startline()
412        self.visit(node.lhs)
413        self.put(u" %s= " % node.operator)
414        self.visit(node.rhs)
415        self.endline()
416
417    def visit_WithStatNode(self, node):
418        self.startline()
419        self.put(u"with ")
420        self.visit(node.manager)
421        if node.target is not None:
422            self.put(u" as ")
423            self.visit(node.target)
424        self.endline(u":")
425        self.indent()
426        self.visit(node.body)
427        self.dedent()
428
429    def visit_TryFinallyStatNode(self, node):
430        self.line(u"try:")
431        self.indent()
432        self.visit(node.body)
433        self.dedent()
434        self.line(u"finally:")
435        self.indent()
436        self.visit(node.finally_clause)
437        self.dedent()
438
439    def visit_TryExceptStatNode(self, node):
440        self.line(u"try:")
441        self.indent()
442        self.visit(node.body)
443        self.dedent()
444        for x in node.except_clauses:
445            self.visit(x)
446        if node.else_clause is not None:
447            self.visit(node.else_clause)
448
449    def visit_ExceptClauseNode(self, node):
450        self.startline(u"except")
451        if node.pattern is not None:
452            self.put(u" ")
453            self.visit(node.pattern)
454        if node.target is not None:
455            self.put(u", ")
456            self.visit(node.target)
457        self.endline(":")
458        self.indent()
459        self.visit(node.body)
460        self.dedent()
461
462    def visit_ReturnStatNode(self, node):
463        self.startline("return ")
464        self.visit(node.value)
465        self.endline()
466
467    def visit_ReraiseStatNode(self, node):
468        self.line("raise")
469
470    def visit_ImportNode(self, node):
471        self.put(u"(import %s)" % node.module_name.value)
472
473    def visit_TempsBlockNode(self, node):
474        """
475        Temporaries are output like $1_1', where the first number is
476        an index of the TempsBlockNode and the second number is an index
477        of the temporary which that block allocates.
478        """
479        idx = 0
480        for handle in node.temps:
481            self.tempnames[handle] = "$%d_%d" % (self.tempblockindex, idx)
482            idx += 1
483        self.tempblockindex += 1
484        self.visit(node.body)
485
486    def visit_TempRefNode(self, node):
487        self.put(self.tempnames[node.handle])
488
489
490class PxdWriter(DeclarationWriter):
491    def __call__(self, node):
492        print u'\n'.join(self.write(node).lines)
493        return node
494
495    def visit_CFuncDefNode(self, node):
496        if 'inline' in node.modifiers:
497            return
498        if node.overridable:
499            self.startline(u'cpdef ')
500        else:
501            self.startline(u'cdef ')
502        if node.visibility != 'private':
503            self.put(node.visibility)
504            self.put(u' ')
505        if node.api:
506            self.put(u'api ')
507        self.visit(node.declarator)
508
509    def visit_StatNode(self, node):
510        pass
511
512
513