diff options
-rw-r--r-- | TurtleArt/tabasics.py | 263 | ||||
-rw-r--r-- | TurtleArt/tablock.py | 37 | ||||
-rw-r--r-- | TurtleArt/tacanvas.py | 2 | ||||
-rw-r--r-- | TurtleArt/taexportpython.py | 228 | ||||
-rw-r--r-- | TurtleArt/talogo.py | 136 | ||||
-rw-r--r-- | TurtleArt/taprimitive.py | 670 | ||||
-rw-r--r-- | TurtleArt/taturtle.py | 3 | ||||
-rw-r--r-- | TurtleArt/tautils.py | 2 | ||||
-rw-r--r-- | TurtleArt/tawindow.py | 3 | ||||
-rw-r--r-- | plugins/turtle_blocks_extras/turtle_blocks_extras.py | 11 | ||||
-rw-r--r-- | pyexported/__init__.py | 0 | ||||
-rw-r--r-- | pyexported/window_setup.py | 186 | ||||
-rwxr-xr-x | turtleblocks.py | 32 | ||||
-rw-r--r-- | util/codegen.py | 571 |
14 files changed, 1972 insertions, 172 deletions
diff --git a/TurtleArt/tabasics.py b/TurtleArt/tabasics.py index 5eac58b..379718d 100644 --- a/TurtleArt/tabasics.py +++ b/TurtleArt/tabasics.py @@ -72,6 +72,8 @@ from tapalette import (make_palette, define_logo_function) from talogo import (primitive_dictionary, logoerror) from tautils import (convert, chr_to_ord, round_int, strtype) from taconstants import (Color, CONSTANTS) +from taprimitive import Primitive +from taturtle import Turtle def _num_type(x): @@ -92,6 +94,12 @@ class Palettes(): def __init__(self, turtle_window): self.tw = turtle_window + self.prim_cache = { + "check_number": Primitive(self.check_number, export_me=False), + "convert_value_for_move": Primitive(self.convert_value_for_move, + export_me=False) + } # avoid several Primitives of the same function + self._turtle_palette() self._pen_palette() @@ -116,7 +124,6 @@ class Palettes(): colors=["#00FF00", "#00A000"], help_string=_('Palette of turtle commands')) - primitive_dictionary['move'] = self._prim_move palette.add_block('forward', style='basic-style-1arg', label=_('forward'), @@ -127,8 +134,9 @@ class Palettes(): self.tw.lc.def_prim( 'forward', 1, - lambda self, x: primitive_dictionary['move']( - self.tw.turtles.get_active_turtle().forward, x)) + Primitive(Turtle.forward, + slot_wrappers={0: self.prim_cache["convert_value_for_move"]}, + call_afterwards=self.after_move)) palette.add_block('back', style='basic-style-1arg', @@ -138,12 +146,12 @@ class Palettes(): logo_command='back', help_string=_('moves turtle backward')) self.tw.lc.def_prim('back', 1, - lambda self, x: - primitive_dictionary['move'] - (self.tw.turtles.get_active_turtle().forward, x, - reverse=True)) + Primitive(Turtle.forward, + slot_wrappers={0: Primitive(Primitive.minus, + slot_wrappers={0: self.prim_cache["convert_value_for_move"] + })}, + call_afterwards=self.after_move)) - primitive_dictionary['clean'] = self._prim_clear palette.add_block('clean', style='basic-style-extended-vertical', label=_('clean'), @@ -154,9 +162,13 @@ turtle')) self.tw.lc.def_prim( 'clean', 0, - lambda self: primitive_dictionary['clean']()) + Primitive(Primitive.group, constant_args={0: [ + Primitive(self.tw.clear_plugins, call_me=False), + Primitive(self.tw.lc.prim_clear_helper, call_me=False, + export_me=False), + Primitive(self.tw.canvas.clearscreen, call_me=False), + Primitive(self.tw.turtles.reset_turtles, call_me=False)]})) - primitive_dictionary['right'] = self._prim_right palette.add_block('left', style='basic-style-1arg', label=_('left'), @@ -166,8 +178,11 @@ turtle')) help_string=_('turns turtle counterclockwise (angle \ in degrees)')) self.tw.lc.def_prim( - 'left', 1, lambda self, - x: primitive_dictionary['right'](x, reverse=True)) + 'left', 1, + Primitive(Turtle.right, + slot_wrappers={0: Primitive(Primitive.minus, + slot_wrappers={0: self.prim_cache["check_number"]})}, + call_afterwards=self.after_right)) palette.add_block('right', style='basic-style-1arg', @@ -180,9 +195,10 @@ degrees)')) self.tw.lc.def_prim( 'right', 1, - lambda self, x: primitive_dictionary['right'](x)) + Primitive(Turtle.right, + slot_wrappers={0: self.prim_cache["check_number"]}, + call_afterwards=self.after_right)) - primitive_dictionary['arc'] = self._prim_arc palette.add_block('arc', style='basic-style-2arg', label=[_('arc'), _('angle'), _('radius')], @@ -193,8 +209,10 @@ degrees)')) self.tw.lc.def_prim( 'arc', 2, - lambda self, x, y: primitive_dictionary['arc']( - self.tw.turtles.get_active_turtle().arc, x, y)) + Primitive(Turtle.arc, + slot_wrappers={0: Primitive(float, export_me=False), + 1: Primitive(float, export_me=False)}, + call_afterwards=self.after_arc)) define_logo_function('taarc', 'to taarc :a :r\nrepeat round :a \ [right 1 forward (0.0175 * :r)]\nend\n') @@ -209,8 +227,12 @@ degrees)')) self.tw.lc.def_prim( 'setxy2', 2, - lambda self, x, y: primitive_dictionary['move']( - self.tw.turtles.get_active_turtle().set_xy, (x, y))) + Primitive(Turtle.set_xy, + slot_wrappers={(0, 2): Primitive(Primitive.make_tuple, + slot_wrappers={0:self.prim_cache["convert_value_for_move"], + 1:self.prim_cache["convert_value_for_move"] + })}, + call_afterwards=self.after_move)) define_logo_function('tasetxy', 'to tasetxy :x :y\nsetxy :x :y\nend\n') primitive_dictionary['set'] = self._prim_set @@ -225,8 +247,9 @@ towards the top of the screen.)')) self.tw.lc.def_prim( 'seth', 1, - lambda self, x: primitive_dictionary['set']( - 'heading', self.tw.turtles.get_active_turtle().set_heading, x)) + Primitive(Turtle.set_heading, + slot_wrappers={0: Primitive(float, export_me=False)}, + call_afterwards=lambda value: self.after_set('heading',value))) palette.add_block('xcor', style='box-style', @@ -239,8 +262,11 @@ the turtle (can be used in place of a number block)'), self.tw.lc.def_prim( 'xcor', 0, - lambda self: self.tw.turtles.get_active_turtle().get_xy()[0] / - self.tw.coord_scale) + Primitive(Primitive.divide, constant_args={ + 0: Primitive(Turtle.get_x, constant_args={ + 0: Primitive(self.tw.turtles.get_active_turtle, + export_me=False)}), + 1: Primitive(self.tw.get_coord_scale)})) palette.add_block('ycor', style='box-style', @@ -253,8 +279,11 @@ the turtle (can be used in place of a number block)'), self.tw.lc.def_prim( 'ycor', 0, - lambda self: self.tw.turtles.get_active_turtle().get_xy()[1] / - self.tw.coord_scale) + Primitive(Primitive.divide, constant_args={ + 0: Primitive(Turtle.get_y, constant_args={ + 0: Primitive(self.tw.turtles.get_active_turtle, + export_me=False)}), + 1: Primitive(self.tw.get_coord_scale)})) palette.add_block('heading', style='box-style', @@ -264,10 +293,7 @@ turtle (can be used in place of a number block)'), value_block=True, prim_name='heading', logo_command='heading') - self.tw.lc.def_prim( - 'heading', - 0, - lambda self: self.tw.turtles.get_active_turtle().get_heading()) + self.tw.lc.def_prim('heading', 0, Primitive(Turtle.get_heading)) palette.add_block('turtle-label', hidden=True, @@ -275,6 +301,7 @@ turtle (can be used in place of a number block)'), label=['turtle']) # Deprecated + primitive_dictionary['move'] = self._prim_move palette.add_block('setxy', hidden=True, style='basic-style-2arg', @@ -312,7 +339,7 @@ shade)')) self.tw.lc.def_prim( 'fillscreen', 2, - lambda self, x, y: self.tw.canvas.fillscreen(x, y)) + Primitive(self.tw.canvas.fillscreen)) palette.add_block('fillscreen2', style='basic-style-3arg', @@ -326,7 +353,7 @@ shade)')) self.tw.lc.def_prim( 'fillscreen2', 3, - lambda self, x, y, z: self.tw.canvas.fillscreen_with_gray(x, y, z)) + Primitive(self.tw.canvas.fillscreen_with_gray)) define_logo_function('tasetbackground', 'to tasetbackground :color \ :shade\ntasetshade :shade\nsetbackground :color\nend\n') @@ -342,8 +369,8 @@ turtle')) self.tw.lc.def_prim( 'setcolor', 1, - lambda self, x: primitive_dictionary['set']( - 'color', self.tw.turtles.get_active_turtle().set_color, x)) + Primitive(Turtle.set_color, + call_afterwards=lambda value: self.after_set('color', value))) palette.add_block('setshade', style='basic-style-1arg', @@ -356,8 +383,8 @@ turtle')) self.tw.lc.def_prim( 'setshade', 1, - lambda self, x: primitive_dictionary['set']( - 'shade', self.tw.turtles.get_active_turtle().set_shade, x)) + Primitive(Turtle.set_shade, + call_afterwards=lambda value: self.after_set('shade', value))) palette.add_block('setgray', style='basic-style-1arg', @@ -369,8 +396,8 @@ the turtle')) self.tw.lc.def_prim( 'setgray', 1, - lambda self, x: primitive_dictionary['set']( - 'gray', self.tw.turtles.get_active_turtle().set_gray, x)) + Primitive(Turtle.set_gray, + call_afterwards=lambda value: self.after_set('gray', value))) palette.add_block('color', style='box-style', @@ -380,10 +407,7 @@ in place of a number block)'), value_block=True, prim_name='color', logo_command='pencolor') - self.tw.lc.def_prim( - 'color', - 0, - lambda self: self.tw.turtles.get_active_turtle().get_color()) + self.tw.lc.def_prim('color', 0, Primitive(Turtle.get_color)) palette.add_block('shade', style='box-style', @@ -392,10 +416,7 @@ in place of a number block)'), value_block=True, prim_name='shade', logo_command=':shade') - self.tw.lc.def_prim( - 'shade', - 0, - lambda self: self.tw.turtles.get_active_turtle().get_shade()) + self.tw.lc.def_prim('shade', 0, Primitive(Turtle.get_shade)) palette.add_block('gray', style='box-style', @@ -404,8 +425,7 @@ in place of a number block)'), used in place of a number block)'), value_block=True, prim_name='gray') - self.tw.lc.def_prim('gray', 0, lambda self: - self.tw.turtles.get_active_turtle().get_gray()) + self.tw.lc.def_prim('gray', 0, Primitive(Turtle.get_gray)) palette.add_block('penup', style='basic-style-extended-vertical', @@ -416,8 +436,7 @@ used in place of a number block)'), self.tw.lc.def_prim( 'penup', 0, - lambda self: - self.tw.turtles.get_active_turtle().set_pen_state(False)) + Primitive(Turtle.set_pen_state, constant_args={0: False})) palette.add_block('pendown', style='basic-style-extended-vertical', @@ -428,8 +447,7 @@ used in place of a number block)'), self.tw.lc.def_prim( 'pendown', 0, - lambda self: - self.tw.turtles.get_active_turtle().set_pen_state(True)) + Primitive(Turtle.set_pen_state, constant_args={0: True})) palette.add_block('penstate', style='boolean-block-style', @@ -439,7 +457,7 @@ used in place of a number block)'), self.tw.lc.def_prim( 'penstate', 0, - lambda self: self.tw.turtles.get_active_turtle().get_pen_state()) + Primitive(Turtle.get_pen_state)) palette.add_block('setpensize', style='basic-style-1arg', @@ -451,8 +469,8 @@ used in place of a number block)'), turtle')) self.tw.lc.def_prim( 'setpensize', 1, - lambda self, x: primitive_dictionary['set'] - ('pensize', self.tw.turtles.get_active_turtle().set_pen_size, x)) + Primitive(Turtle.set_pen_size, + call_afterwards=lambda val: self.after_set('pensize', val))) define_logo_function('tasetpensize', 'to tasetpensize :a\nsetpensize round :a\nend\n') @@ -465,7 +483,7 @@ fill block)')) self.tw.lc.def_prim( 'startfill', 0, - lambda self: self.tw.turtles.get_active_turtle().start_fill()) + Primitive(Turtle.start_fill)) palette.add_block('stopfill', style='basic-style-extended-vertical', @@ -476,7 +494,7 @@ start fill block)')) self.tw.lc.def_prim( 'stopfill', 0, - lambda self: self.tw.turtles.get_active_turtle().stop_fill()) + Primitive(Turtle.stop_fill)) palette.add_block('pensize', style='box-style', @@ -489,7 +507,7 @@ in place of a number block)'), self.tw.lc.def_prim( 'pensize', 0, - lambda self: self.tw.turtles.get_active_turtle().get_pen_size()) + Primitive(Turtle.get_pen_size)) define_logo_function('tapensize', 'to tapensize\noutput first round \ pensize\nend\n') @@ -745,9 +763,8 @@ operators')) prim_name='not', logo_command='not', help_string=_('logical NOT operator')) - self.tw.lc.def_prim('not', 1, lambda self, x: not x) + self.tw.lc.def_prim('not', 1, Primitive(Primitive.not_)) - primitive_dictionary['and'] = self._prim_and palette.add_block('and2', style='boolean-style', label=_('and'), @@ -756,9 +773,8 @@ operators')) special_name=_('and'), help_string=_('logical AND operator')) self.tw.lc.def_prim( - 'and', 2, lambda self, x, y: primitive_dictionary['and'](x, y)) + 'and', 2, Primitive(Primitive.and_)) - primitive_dictionary['or'] = self._prim_or palette.add_block('or2', style='boolean-style', label=_('or'), @@ -767,7 +783,7 @@ operators')) special_name=_('or'), help_string=_('logical OR operator')) self.tw.lc.def_prim( - 'or', 2, lambda self, x, y: primitive_dictionary['or'](x, y)) + 'or', 2, Primitive(Primitive.or_)) def _flow_palette(self): ''' The basic Turtle Art flow palette ''' @@ -795,8 +811,11 @@ number of seconds')) default=[None, None], logo_command='forever', help_string=_('loops forever')) - self.tw.lc.def_prim('forever', 1, primitive_dictionary['forever'], - True) + self.tw.lc.def_prim('forever', 1, + Primitive(self.tw.lc.prim_loop, + constant_args={0: Primitive(Primitive.controller_forever, + call_me=False)}), + True) primitive_dictionary['repeat'] = self._prim_repeat palette.add_block('repeat', @@ -807,9 +826,14 @@ number of seconds')) logo_command='repeat', special_name=_('repeat'), help_string=_('loops specified number of times')) - self.tw.lc.def_prim('repeat', 2, primitive_dictionary['repeat'], True) + self.tw.lc.def_prim('repeat', 2, + Primitive(self.tw.lc.prim_loop, + slot_wrappers={0: Primitive(Primitive.controller_repeat, + slot_wrappers={0: Primitive(self.tw.lc.int, + slot_wrappers={0: self.prim_cache["check_number"] + })})}), + True) - primitive_dictionary['if'] = self._prim_if palette.add_block('if', style='clamp-style-boolean', label=[_('if'), _('then'), ''], @@ -819,9 +843,8 @@ number of seconds')) logo_command='if', help_string=_('if-then operator that uses boolean \ operators from Numbers palette')) - self.tw.lc.def_prim('if', 2, primitive_dictionary['if'], True) + self.tw.lc.def_prim('if', 2, Primitive(self.tw.lc.prim_if), True) - primitive_dictionary['ifelse'] = self._prim_ifelse palette.add_block('ifelse', hidden=True, # Too big to fit palette style='clamp-style-else', @@ -832,7 +855,8 @@ operators from Numbers palette')) special_name=_('if then else'), help_string=_('if-then-else operator that uses \ boolean operators from Numbers palette')) - self.tw.lc.def_prim('ifelse', 3, primitive_dictionary['ifelse'], True) + self.tw.lc.def_prim('ifelse', 3, Primitive(self.tw.lc.prim_ifelse), + True) # macro palette.add_block('ifthenelse', @@ -849,7 +873,8 @@ boolean operators from Numbers palette')) prim_name='nop', special_name=_('horizontal space'), help_string=_('jogs stack right')) - self.tw.lc.def_prim('nop', 0, lambda self: None) + self.tw.lc.def_prim('nop', 0, + Primitive(Primitive.do_nothing, export_me=False)) palette.add_block('vspace', style='basic-style-extended-vertical', @@ -857,7 +882,8 @@ boolean operators from Numbers palette')) prim_name='nop', special_name=_('vertical space'), help_string=_('jogs stack down')) - self.tw.lc.def_prim('nop', 0, lambda self: None) + self.tw.lc.def_prim('nop', 0, + Primitive(Primitive.do_nothing, export_me=False)) primitive_dictionary['stopstack'] = self._prim_stopstack palette.add_block('stopstack', @@ -876,7 +902,6 @@ boolean operators from Numbers palette')) colors=["#FFFF00", "#A0A000"], help_string=_('Palette of variable blocks')) - primitive_dictionary['start'] = self._prim_start palette.add_block('start', style='basic-style-head', label=_('start'), @@ -885,7 +910,7 @@ boolean operators from Numbers palette')) help_string=_('connects action to toolbar run \ buttons')) self.tw.lc.def_prim('start', 0, - lambda self: primitive_dictionary['start']()) + Primitive(self.tw.lc.prim_start, export_me=False)) palette.add_block('string', style='box-style', @@ -1062,9 +1087,7 @@ variable')) ''' Logical and ''' return x & y - def _prim_arc(self, cmd, value1, value2): - ''' Turtle draws an arc of degree, radius ''' - cmd(float(value1), float(value2)) + def after_arc(self, *ignored_args): if self.tw.lc.update_values: self.tw.lc.update_label_value( 'xcor', @@ -1097,46 +1120,39 @@ variable')) break self.tw.lc.ireturn() yield True - - def _prim_if(self, boolean, blklist): - ''' If bool, do list ''' - if boolean: - self.tw.lc.icall(self.tw.lc.evline, blklist[:]) - yield True - self.tw.lc.ireturn() - yield True - - def _prim_ifelse(self, boolean, list1, list2): - ''' If bool, do list1, else do list2 ''' - if boolean: - self.tw.lc.ijmp(self.tw.lc.evline, list1[:]) - yield True + + def convert_value_for_move(self, value): + ''' Perform type conversion and other preprocessing on the parameter, + so it can be passed to the 'move' primitive. ''' + if value is None: + return value + + def _convert_to_float(val): + if not _num_type(val): + raise logoerror("#notanumber") + return float(val) + + if isinstance(value, (tuple, list)): + (val1, val2) = value + val1_float = _convert_to_float(val1) + val2_float = _convert_to_float(val2) + value_converted = (val1_float, val2_float) else: - self.tw.lc.ijmp(self.tw.lc.evline, list2[:]) - yield True + value_converted = _convert_to_float(value) + return value_converted - def _prim_move(self, cmd, value1, value2=None, pendown=True, + def _prim_move(self, cmd, value1, pendown=True, reverse=False): ''' Turtle moves by method specified in value1 ''' - pos = None - if isinstance(value1, (tuple, list)): - pos = value1 - value1 = pos[0] - value2 = pos[1] - if not _num_type(value1): - raise logoerror("#notanumber") - if value2 is None: - if reverse: - cmd(float(-value1)) - else: - cmd(float(value1)) - else: - if not _num_type(value2): - raise logoerror("#notanumber") - if pos is not None: - cmd((float(value1), float(value2)), pendown=pendown) - else: - cmd(float(value1), float(value2), pendown=pendown) + + value1_conv = self.convert_value_for_move(value1) + + cmd(value1_conv, pendown=pendown) + + self.after_move() + + def after_move(self, *ignored_args): + ''' Update labels after moving the turtle ''' if self.tw.lc.update_values: self.tw.lc.update_label_value( 'xcor', @@ -1163,15 +1179,15 @@ variable')) break self.tw.lc.ireturn() yield True - - def _prim_right(self, value, reverse=False): - ''' Turtle rotates clockwise ''' + + def check_number(self, value): + ''' Check if value is a number. If yes, return the value. If no, + raise an error. ''' if not _num_type(value): raise logoerror("#notanumber") - if reverse: - self.tw.turtles.get_active_turtle().right(float(-value)) - else: - self.tw.turtles.get_active_turtle().right(float(value)) + return value + + def after_right(self, *ignored_args): if self.tw.lc.update_values: self.tw.lc.update_label_value( 'heading', @@ -1184,6 +1200,12 @@ variable')) if self.tw.lc.update_values: self.tw.lc.update_label_value(name, value) + def after_set(self, name, value=None): + ''' Update the associated value blocks ''' + if value is not None: + if self.tw.lc.update_values: + self.tw.lc.update_label_value(name, value) + def _prim_setbox(self, name, x, val): ''' Define value of named box ''' if x is not None: @@ -1234,11 +1256,6 @@ variable')) self.tw.lc.ireturn() yield True - def _prim_start(self): - ''' Start block: recenter ''' - if self.tw.running_sugar: - self.tw.activity.recenter() - def _prim_stopstack(self): ''' Stop execution of a stack ''' self.tw.lc.procstop = True diff --git a/TurtleArt/tablock.py b/TurtleArt/tablock.py index 1ec5e9e..13f50ae 100644 --- a/TurtleArt/tablock.py +++ b/TurtleArt/tablock.py @@ -24,7 +24,8 @@ import cairo from taconstants import (EXPANDABLE, EXPANDABLE_ARGS, OLD_NAMES, CONSTANTS, STANDARD_STROKE_WIDTH, BLOCK_SCALE, BOX_COLORS, - GRADIENT_COLOR, EXPANDABLE_FLOW, Color) + GRADIENT_COLOR, EXPANDABLE_FLOW, Color, + PREFIX_DICTIONARY) from tapalette import (palette_blocks, block_colors, expandable_blocks, content_blocks, block_names, block_primitives, block_styles, special_block_colors) @@ -34,6 +35,9 @@ import sprites from tautils import (debug_output, error_output) +media_blocks_dictionary = {} # new media blocks get added here + + class Blocks: """ A class for the list of blocks and everything they share in common """ @@ -277,6 +281,37 @@ class Block: return False return True + def is_value_block(self): + """ Return True iff this block is a value block (numeric, string, + media, etc.) """ + return self.primitive is None and self.values + + def get_value(self): + """ Return the value stored in this value block """ + # TODO what error to raise if this is not a value block? + if self.name == 'number': + try: + return float(self.values[0]) + except ValueError: + return float(ord(self.values[0][0])) + elif (self.name == 'string' or + self.name == 'title'): # deprecated block + if isinstance(self.values[0], (float, int)): + if int(self.values[0]) == self.values[0]: + self.values[0] = int(self.values[0]) + return '#s' + str(self.values[0]) + else: + return '#s' + self.values[0] + elif self.name in PREFIX_DICTIONARY: + if self.values[0] is not None: + return PREFIX_DICTIONARY[self.name] + str(self.values[0]) + else: + return PREFIX_DICTIONARY[self.name] + 'None' + elif self.name in media_blocks_dictionary: + return '#smedia_' + self.name.upper() + else: + return '%nothing%' + def highlight(self): """ We may want to highlight a block... """ if self.spr is not None and self.status is not 'collapsed': diff --git a/TurtleArt/tacanvas.py b/TurtleArt/tacanvas.py index d3c4b3f..4bac442 100644 --- a/TurtleArt/tacanvas.py +++ b/TurtleArt/tacanvas.py @@ -1,4 +1,4 @@ -31#Copyright (c) 2007-8, Playful Invention Company. +#Copyright (c) 2007-8, Playful Invention Company. #Copyright (c) 2008-11, Walter Bender #Copyright (c) 2011 Collabora Ltd. <http://www.collabora.co.uk/> diff --git a/TurtleArt/taexportpython.py b/TurtleArt/taexportpython.py new file mode 100644 index 0000000..8be59bf --- /dev/null +++ b/TurtleArt/taexportpython.py @@ -0,0 +1,228 @@ +#Copyright (c) 2013 Marion Zepf + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + +""" Python export tool """ + +import ast +from gettext import gettext as _ +from os import linesep +import re +import traceback +import util.codegen as codegen + +#from ast_pprint import * # only used for debugging, safe to comment out + +from talogo import LogoCode +from taprimitive import (Primitive, PyExportError, value_to_ast) +from tautils import (debug_output, find_group, find_top_block) + + + +_SETUP_CODE_START = """\ +#!/usr/bin/env python + +from pyexported.window_setup import * + + +tw = get_tw() + +BOX = {} +ACTION = {} + + + +""" +_SETUP_CODE_END = """\ + + + +if __name__ == '__main__': + tw.lc.icall(start) + gobject.idle_add(tw.lc.doevalstep) + gtk.main() + + +""" +_ACTION_STACK_START = """\ +def %s(): + turtle = tw.turtles.get_active_turtle() + turtles = tw.turtles + canvas = tw.canvas + logo = tw.lc + +""" +_ACTION_STACK_END = """\ +ACTION["%s"] = %s +""" +# character that is illegal in a Python identifier +PAT_IDENTIFIER_ILLEGAL_CHAR = re.compile("[^A-Za-z0-9_]") + + + +def save_python(tw): + """ Find all the action stacks and turn each into python code """ + all_blocks = tw.just_blocks() + blocks_covered = set() + tops_of_stacks = [] + for block in all_blocks: + if block not in blocks_covered: + top = find_top_block(block) + tops_of_stacks.append(top) + block_stack = find_group(top) + blocks_covered.update(set(block_stack)) + + snippets = [_SETUP_CODE_START] + for block in tops_of_stacks: + # TODO name of action stack? + pythoncode = _action_stack_to_python(block, tw.lc) + snippets.append(pythoncode) + snippets.append(linesep) + snippets.append(_SETUP_CODE_END) + return "".join(snippets) + +def _action_stack_to_python(block, lc, name="start"): + """ Turn a stack of blocks into python code + name -- the name of the action stack (defaults to "start") """ + # traverse the block stack and get the AST for every block + ast_list = _walk_action_stack(block, lc) + ast_list.append(_ast_yield_true()) + action_stack_ast = ast.Module(body=ast_list) + #debug_output(str(action_stack_ast)) + + # serialize the ASTs into python code + generated_code = codegen.to_source(action_stack_ast) + + # wrap the action stack setup code around everything + name_id = _make_identifier(name) + generated_code = _indent(generated_code, 1) + if generated_code.endswith(linesep): + newline = "" + else: + newline = linesep + snippets = [_ACTION_STACK_START % (name_id), + generated_code, + newline, + _ACTION_STACK_END % (name, name_id)] + return "".join(snippets) + +def _walk_action_stack(top_block, lc): + """ Turn a stack of blocks into a list of ASTs """ + block = top_block + + # value blocks don't have a primitive + if block.is_value_block(): + raw_value = block.get_value() + value_ast = value_to_ast(raw_value) + if value_ast is not None: + return [value_ast] + else: + return [] + + def _get_prim(block): + prim = lc.get_prim_callable(block.primitive) + # fail gracefully if primitive is not a Primitive object + if not isinstance(prim, Primitive): + raise PyExportError(_("block is not exportable"), block=block) + return prim + + prim = _get_prim(block) + + ast_list = [] + arg_asts = [] + + def _finish_off(block, prim=None): + """ Convert block to an AST and add it to the ast_list. Raise a + PyExportError on failure. """ + if prim is None: + prim = _get_prim(block) + if prim.export_me: + try: + new_ast = prim.get_ast(*arg_asts) + except ValueError: + traceback.print_exc() + raise PyExportError(_("error while exporting block"), + block=block) + if isinstance(new_ast, (list, tuple)): + ast_list.extend(new_ast) + else: + ast_list.append(new_ast) + elif arg_asts: + new_ast = ast.List(elts=arg_asts, ctx=ast.Load) + ast_list.append(new_ast) + + # skip the very first dock/ connection - it's either the previous block or + # the return value of this block + dock_queue = block.docks[1:] + conn_queue = block.connections[1:] + while dock_queue and conn_queue: + dock = dock_queue.pop(0) + conn = conn_queue.pop(0) + if conn is None or dock[0] == 'unavailable': + continue + elif not dock_queue and dock[0] == 'flow': + # finish off this block + _finish_off(block, prim) + arg_asts = [] + # next block + block = conn + prim = _get_prim(block) + dock_queue = block.docks[1:] + conn_queue = block.connections[1:] + else: + # embedded stack of blocks (body of conditional or loop) or + # argument block + new_arg_asts = _walk_action_stack(conn, lc) + if dock[0] == 'flow': + # body of conditional or loop + if prim == LogoCode.prim_loop: + new_arg_asts.append(_ast_yield_true()) + arg_asts.append(new_arg_asts) + else: + # argument block + arg_asts.append(*new_arg_asts) + + # finish off last block + _finish_off(block, prim) + + return ast_list + +def _make_identifier(name): + """ Turn name into a Python identifier name by replacing illegal + characters """ + replaced = re.sub(PAT_IDENTIFIER_ILLEGAL_CHAR, "_", name) + # TODO find better strategy to avoid number at beginning + if re.match("[0-9]", replaced): + replaced = "_" + replaced + return replaced + +def _indent(code, num_levels=1): + """ Indent each line of code with num_levels * 4 spaces + code -- some python code as a (multi-line) string """ + indentation = " " * (4 * num_levels) + line_list = code.split(linesep) + new_line_list = [] + for line in line_list: + new_line_list.append(indentation + line) + return linesep.join(new_line_list) + +def _ast_yield_true(): + return ast.Yield(value=ast.Name(id='True', ctx=ast.Load)) + + diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py index 7aac4ce..4512387 100644 --- a/TurtleArt/talogo.py +++ b/TurtleArt/talogo.py @@ -33,7 +33,10 @@ try: except ImportError: GRID_CELL_SIZE = 55 -from taconstants import (TAB_LAYER, DEFAULT_SCALE, PREFIX_DICTIONARY) +import traceback + +from tablock import (Block, media_blocks_dictionary) +from taconstants import (TAB_LAYER, DEFAULT_SCALE) from tapalette import (block_names, value_blocks) from tautils import (get_pixbuf_from_journal, convert, data_from_file, text_media_type, round_int, debug_output, find_group) @@ -46,7 +49,6 @@ except ImportError: from gettext import gettext as _ -media_blocks_dictionary = {} # new media blocks get added here primitive_dictionary = {} # new block primitives get added here @@ -79,7 +81,7 @@ class logoerror(Exception): return str(self.value) -class HiddenBlock: +class HiddenBlock(Block): def __init__(self, name, value=None): self.name = name @@ -92,6 +94,7 @@ class HiddenBlock: self.connections = [] self.docks = [] + # Utility functions @@ -187,6 +190,10 @@ class LogoCode: self.oblist[string] = sym return sym + def get_prim_callable(self, name): + """ Return the callable primitive associated with the given name """ + return self.oblist[name].fcn + def run_blocks(self, code): """Run code generated by generate_code(). """ @@ -286,30 +293,12 @@ class LogoCode: self.tw.block_list.list.index(blk))) else: code.append(blk.primitive) # Hidden block - elif len(blk.values) > 0: # Extract the value from content blocks. - if blk.name == 'number': - try: - code.append(float(blk.values[0])) - except ValueError: - code.append(float(ord(blk.values[0][0]))) - elif blk.name == 'string' or \ - blk.name == 'title': # deprecated block - if isinstance(blk.values[0], (float, int)): - if int(blk.values[0]) == blk.values[0]: - blk.values[0] = int(blk.values[0]) - code.append('#s' + str(blk.values[0])) - else: - code.append('#s' + blk.values[0]) - elif blk.name in PREFIX_DICTIONARY: - if blk.values[0] is not None: - code.append(PREFIX_DICTIONARY[blk.name] + - str(blk.values[0])) - else: - code.append(PREFIX_DICTIONARY[blk.name] + 'None') - elif blk.name in media_blocks_dictionary: - code.append('#smedia_' + blk.name.upper()) - else: + elif blk.is_value_block(): # Extract the value from content blocks. + value = blk.get_value() + if value == '%nothing%': return ['%nothing%'] + else: + code.append(value) else: return ['%nothing%'] if blk.connections is not None and len(blk.connections) > 0: @@ -528,28 +517,40 @@ class LogoCode: if self.step is not None: try: self.step.next() - except ValueError: - debug_output('generator already executing', - self.tw.running_sugar) - self.tw.running_blocks = False + except ValueError, ve: + if self.tw.running_turtleart: + debug_output('generator already executing', + self.tw.running_sugar) + self.tw.running_blocks = False + else: + traceback.print_exc() + self.tw.showlabel('status', 'ValueError: ' + + str(ve)) return False else: return False except StopIteration: - # self.tw.turtles.show_all() - if self.hidden_turtle is not None: - self.hidden_turtle.show() - self.hidden_turtle = None + if self.tw.running_turtleart: + # self.tw.turtles.show_all() + if self.hidden_turtle is not None: + self.hidden_turtle.show() + self.hidden_turtle = None + else: + self.tw.turtles.get_active_turtle().show() + self.tw.running_blocks = False + return False else: - self.tw.turtles.get_active_turtle().show() - self.tw.running_blocks = False - return False + self.ireturn() except logoerror, e: - self.tw.showblocks() - self.tw.display_coordinates() - self.tw.showlabel('syntaxerror', str(e)) - self.tw.turtles.show_all() - self.tw.running_blocks = False + if self.tw.running_turtleart: + self.tw.showblocks() + self.tw.display_coordinates() + self.tw.showlabel('syntaxerror', str(e)) + self.tw.turtles.show_all() + self.tw.running_blocks = False + else: + traceback.print_exc() + self.tw.showlabel('status', 'logoerror: ' + str(e)) return False return True @@ -596,19 +597,64 @@ class LogoCode: name.nargs, name.fcn = 0, body name.rprim = True + def prim_start(self, *ignored_args): + ''' Start block: recenter ''' + if self.tw.running_sugar: + self.tw.activity.recenter() + def prim_clear(self): """ Clear screen """ self.tw.clear_plugins() + self.prim_clear_helper() + self.tw.canvas.clearscreen() + self.tw.turtles.reset_turtles() + + def prim_clear_helper(self): if self.tw.gst_available: from tagplay import stop_media stop_media(self) - self.tw.canvas.clearscreen() - self.tw.turtles.reset_turtles() self.scale = DEFAULT_SCALE self.hidden_turtle = None self.start_time = time() self.clear_value_blocks() - self.tw.activity.restore_state() + if self.tw.running_turtleart: + self.tw.activity.restore_state() + + def prim_loop(self, controller, blklist): + """ Execute a loop + controller -- iterator that yields True iff the loop should be run + once more OR a callable that returns such an iterator + blklist -- list of callables that form the loop body """ + if not hasattr(controller, "next"): + if callable(controller): + controller = controller() + else: + raise TypeError("a loop controller must be either an iterator " + "or a callable that returns an iterator") + while next(controller): + self.icall(self.evline, blklist[:]) + yield True + if self.procstop: + break + self.ireturn() + yield True + + def prim_if(self, boolean, blklist): + """ If bool, do list """ + if boolean: + self.icall(self.evline, blklist[:]) + yield True + self.ireturn() + yield True + + def prim_ifelse(self, boolean, list1, list2): + """ If bool, do list1, else do list2 """ + if boolean: + self.ijmp(self.evline, list1[:]) + yield True + else: + self.ijmp(self.evline, list2[:]) + yield True def clear_value_blocks(self): if not hasattr(self, 'value_blocks_to_update'): diff --git a/TurtleArt/taprimitive.py b/TurtleArt/taprimitive.py new file mode 100644 index 0000000..7530aa5 --- /dev/null +++ b/TurtleArt/taprimitive.py @@ -0,0 +1,670 @@ +#Copyright (c) 2013 Marion Zepf + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + +import ast +from gettext import gettext as _ + +#from ast_pprint import * # only used for debugging, safe to comment out + +from tacanvas import TurtleGraphics +from talogo import LogoCode +from taturtle import (Turtle, Turtles) +from tautils import debug_output +from tawindow import TurtleArtWindow + + +class PyExportError(BaseException): + """ Error that is raised when something goes wrong while converting the + blocks to python code """ + + def __init__(self, message, block=None): + """ message -- the error message + block -- the block where the error occurred """ + self.message = message + self.block = block + + def __str__(self): + if self.block is not None: + return _("error in highlighted block") + ": " + str(self.message) + else: + return _("error") + ": " + str(self.message) + + +class Primitive(object): + """ Something that can be called when the block code is executed in TA, + but that can also be transformed into a Python AST. + """ + + STANDARD_OPERATORS = {'plus': (ast.UAdd, ast.Add), + 'minus': (ast.USub, ast.Sub), + 'multiply': ast.Mult, + 'divide': ast.Div, + 'modulo': ast.Mod, + 'power': ast.Pow, + 'integer_division': ast.FloorDiv, + 'bitwise_and': ast.BitAnd, + 'bitwise_or': ast.BitOr, + 'and_': ast.And, + 'or_': ast.Or, + 'not_': ast.Not} + + def __init__(self, func, constant_args=None, slot_wrappers=None, + call_afterwards=None, call_me=True, export_me=True): + """ constant_args -- A dictionary containing constant arguments to be + passed to the function. It uses the same key scheme as + slot_wrappers, except that argument ranges are not supported. + The constant args and kwargs are added to the runtime args and + kwargs before the slot wrappers are called. + slot_wrappers -- A dictionary mapping from the index of an + argument in the args list to another Primitive that should be + wrapped around the actual argument value (e.g., to convert a + positive number to a negative one). For keyword arguments, the + key in slot_wrappers should be the same as the kwargs key. To pass + multiple arguments to the slot wrapper, use a tuple of the first + and last argument number (the latter increased by 1) as a key. + Negative argument indices are not supported. + call_afterwards -- Code to call after this Primitive has been called + (e.g., for updating labels in LogoCode) (not used for creating + AST) + call_me -- True if this Primitive should be called (default), False + if it should be passed on as a Primitive object + export_me -- True iff this Primitive should be exported to Python + code (the default case) """ + self.func = func + + if constant_args is None: + self.constant_args = {} + else: + self.constant_args = constant_args + + if slot_wrappers is None: + self.slot_wrappers = {} + else: + # check for duplicate argument indices + msg = ("argument at index %d is associated with multiple slot " + "wrappers") + nums = set() + tuples = [] + for k in slot_wrappers.keys(): + if isinstance(k, int): + nums.add(k) + elif isinstance(k, tuple): + tuples.append(k) + tuples.sort() + prev_tuple = (0, 0) + for tuple_ in tuples: + if prev_tuple[1] > tuple_[0]: + raise KeyError(msg % (tuple_[0])) + for i in range(*tuple_): + if i in nums: + raise KeyError(msg % (i)) + prev_tuple = tuple_ + self.slot_wrappers = slot_wrappers + + self.call_afterwards = call_afterwards + self.call_me = call_me + self.export_me = export_me + + def __repr__(self): + return "Primitive(" + repr(self.func) + ")" + + def _apply_wrappers(self, runtime_args, runtime_kwargs, + convert_to_ast=False): + """ Apply the slot wrappers """ + # make a map from the start indices of all ranges to their ends + range_ends = {} + for range_tuple in sorted(self.slot_wrappers.keys()): + if isinstance(range_tuple, tuple): + (start, end) = range_tuple + range_ends[start] = end + + new_args = [] + i = 0 + while i < len(runtime_args): + arg = runtime_args[i] + wrapper = self.slot_wrappers.get(i) + if wrapper is None: + (start, end) = (i, range_ends.get(i)) + if end is None: + # no slot wrapper found + # convert to AST, but don't call + if convert_to_ast and isinstance(arg, Primitive): + new_args.append(arg.get_ast()) + else: + new_args.append(arg) + i += 1 + else: + # range -> slot wrapper around a range of arguments + wrapper = self.slot_wrappers.get((start, end)) + args_for_wrapper = runtime_args[start:end] + if not convert_to_ast and call_me(wrapper): + wrapper_output = wrapper(*args_for_wrapper) + elif convert_to_ast and export_me(wrapper): + wrapper_output = value_to_ast(wrapper, + *args_for_wrapper) + else: + wrapper_output = args_for_wrapper + new_args.append(wrapper_output) + i += end - start + else: + # number -> slot wrapper around one argument + if not convert_to_ast and call_me(wrapper): + new_arg = wrapper(arg) + elif convert_to_ast and export_me(wrapper): + new_arg = value_to_ast(wrapper, arg) + else: + new_arg = arg + new_args.append(new_arg) + i += 1 + + new_kwargs = {} + for (key, value) in runtime_kwargs.iteritems(): + wrapper = self.slot_wrappers.get(key) + if wrapper is not None: + if not convert_to_ast and call_me(wrapper): + new_value = wrapper(value) + elif convert_to_ast and export_me(wrapper): + new_value = value_to_ast(wrapper, value) + else: + new_value = value + new_kwargs[key] = new_value + else: + new_kwargs[key] = value + + return (new_args, new_kwargs) + + def _add_constant_args(self, runtime_args, runtime_kwargs, + convert_to_ast=False): + """ Add the constant args and kwargs to the given runtime args and + kwargs. Return a list containing all args and a dictionary with all + kwargs. + convert_to_ast -- convert all constant arguments to ASTs? """ + all_args = [] + all_kwargs = runtime_kwargs.copy() + + # args + i = 0 + def _insert_c_args(i): + while i in self.constant_args: + c_arg = self.constant_args[i] + if not convert_to_ast and call_me(c_arg): + all_args.append(c_arg()) + elif convert_to_ast: + if export_me(c_arg): + all_args.append(value_to_ast(c_arg)) + else: + all_args.append(c_arg) + i += 1 + return i + for arg in runtime_args: + i = _insert_c_args(i) + all_args.append(arg) + i += 1 + i = _insert_c_args(i) + + # kwargs + for (key, value) in self.constant_args.iteritems(): + if isinstance(key, basestring): + if not convert_to_ast and call_me(value): + all_kwargs[key] = value() + elif convert_to_ast: + if export_me(value): + all_kwargs[key] = value_to_ast(value) + else: + all_kwargs[key] = value + + return (all_args, all_kwargs) + + def __call__(self, *runtime_args, **runtime_kwargs): + """ Execute the function, passing it the arguments received at + runtime. Also call the function in self.call_afterwards and pass it + all runtime_args and runtime_kwargs. + If the very first argument is a LogoCode instance, it may be + replaced with the active turtle, the canvas, or nothing (depending + on what this primitive wants as its first arg). This argument is + also exempt from the slot wrappers. """ + + # replace or remove the first argument if it is a LogoCode instance + first_arg = None + if runtime_args and isinstance(runtime_args[0], LogoCode): + (lc, runtime_args) = (runtime_args[0], runtime_args[1:]) + if self.wants_turtle(): + first_arg = lc.tw.turtles.get_active_turtle() + elif self.wants_turtles(): + first_arg = lc.tw.turtles + elif self.wants_canvas(): + first_arg = lc.tw.canvas + elif self.wants_logocode(): + first_arg = lc + elif self.wants_tawindow(): + first_arg = lc.tw + + # constant arguments + (all_args, all_kwargs) = self._add_constant_args(runtime_args, + runtime_kwargs) + + # slot wrappers + (new_args, new_kwargs) = self._apply_wrappers(all_args, all_kwargs) + + # execute the actual function + if first_arg is None or is_bound_instancemethod(self.func): + return_value = self.func(*new_args, **new_kwargs) + else: + return_value = self.func(first_arg, *new_args, **new_kwargs) + + if self.call_afterwards is not None: + self.call_afterwards(*new_args, **new_kwargs) + + return return_value + + def get_ast(self, *arg_asts, **kwarg_asts): + """ Transform this object into a Python AST. When serialized and + executed, the AST will do exactly the same as calling this object. """ + + # constant arguments + (all_arg_asts, all_kwarg_asts) = self._add_constant_args(arg_asts, + kwarg_asts, convert_to_ast=True) + + # slot wrappers + (new_arg_asts, new_kwarg_asts) = self._apply_wrappers(all_arg_asts, + all_kwarg_asts, + convert_to_ast=True) + + # SPECIAL HANDLING # + + # loops + if self == LogoCode.prim_loop: + controller = self._get_loop_controller() + if controller == Primitive.controller_repeat: + # 'repeat' loop + num_repetitions = new_arg_asts[0] + if num_repetitions.func.id == 'controller_repeat': + num_repetitions = num_repetitions.args[0] + repeat_iter = ast.Call(func=ast.Name(id="range", ctx=ast.Load), + args=[num_repetitions], + keywords={}, + starargs=None, + kwargs=None) + # TODO use new variable name in nested loops + loop_ast = ast.For(target=ast.Name(id="i", ctx=ast.Store), + iter=repeat_iter, + body=new_arg_asts[1], + orelse=[]) + return loop_ast + else: + if controller == Primitive.controller_forever: + condition_ast = ast.Name(id="True", ctx=ast.Load) + elif controller == Primitive.controller_while: + condition_ast = new_arg_asts[0].args[0] + elif controller == Primitive.controller_until: + condition_ast = ast.UnaryOp(op=ast.Not, + operand=new_arg_asts[0].args[0]) + else: + raise ValueError("unknown loop controller: " + + repr(controller)) + loop_ast = ast.While(test=condition_ast, + body=new_arg_asts[1], + orelse=[]) + return loop_ast + + # conditionals + elif self in (LogoCode.prim_if, LogoCode.prim_ifelse): + test = new_arg_asts[0] + body = new_arg_asts[1] + if len(new_arg_asts) > 2: + orelse = new_arg_asts[2] + else: + orelse = [] + if_ast = ast.If(test=test, body=body, orelse=orelse) + return if_ast + + # standard operators + elif self.func.__name__ in Primitive.STANDARD_OPERATORS: + op = Primitive.STANDARD_OPERATORS[self.func.__name__] + if len(new_arg_asts) == 1: + if isinstance(op, tuple): + op = op[0] + return ast.UnaryOp(op=op, operand=new_arg_asts[0]) + elif len(new_arg_asts) == 2: + if isinstance(op, tuple): + op = op[1] + (left, right) = new_arg_asts + if op in (ast.And, ast.Or): + return ast.BoolOp(op=op, values=[left, right]) + else: + return ast.BinOp(op=op, left=left, right=right) + else: + raise ValueError(("operator Primitive.%s got unexpected" + " number of arguments (%d)") + % (str(self.func.__func__.__name__), + len(new_arg_asts))) + + # tuples + elif self == Primitive.make_tuple: + if not new_kwarg_asts: + return ast.Tuple(elts=new_arg_asts, ctx=ast.Load) + else: + raise ValueError("tuple constructor (Primitive.make_tuple) " + "got unexpected arguments: " + + repr(new_kwarg_asts)) + + # group of Primitives + elif self == Primitive.group: + return new_arg_asts[0].elts + + # NORMAL FUNCTION CALL # + + else: + func_name = "" + if self.wants_turtle(): + func_name = "turtle." + elif self.wants_turtles(): + func_name = "turtles." + elif self.wants_canvas(): + func_name = "canvas." + elif self.wants_logocode(): + func_name = "logo." + elif self.wants_tawindow(): + func_name = "tw." + # get the name of the function directly from the function itself + func_name += self.func.__name__ + + return ast.Call(func=ast.Name(id=func_name, ctx=ast.Load), + args=new_arg_asts, + keywords=new_kwarg_asts, + starargs=None, + kwargs=None) + + def __eq__(self, other): + """ Two Primitives are equal iff their all their properties are equal. + Consider bound and unbound methods equal. """ + # other is a Primitive + if isinstance(other, Primitive): + return (self == other.func and + self.constant_args == other.constant_args and + self.slot_wrappers == other.slot_wrappers and + self.call_afterwards == other.call_afterwards and + self.export_me == other.export_me) + + # other is a callable + elif callable(other): + if is_instancemethod(self.func) != is_instancemethod(other): + return False + elif is_instancemethod(self.func): # and is_instancemethod(other): + return (self.func.im_class == other.im_class and + self.func.im_func == other.im_func) + else: + return self.func == other + + elif is_staticmethod(other): + return self.func == other.__func__ + + # other is neither a Primitive nor a callable + else: + return False + + def wants_turtle(self): + """ Does this Primitive want to get the active turtle as its first + argument? """ + return self._wants(Turtle) + + def wants_turtles(self): + """ Does this Primitive want to get the Turtles instance as its + first argument? """ + return self._wants(Turtles) + + def wants_canvas(self): + """ Does this Primitive want to get the canvas as its first + argument? """ + return self._wants(TurtleGraphics) + + def wants_logocode(self): + """ Does this Primitive want to get the LogoCode instance as its + first argument? """ + return self._wants(LogoCode) + + def wants_tawindow(self): + """ Does this Primitive want to get the TurtleArtWindow instance + as its first argument? """ + return self._wants(TurtleArtWindow) + + def wants_nothing(self): + """ Does this Primitive want nothing as its first argument? I.e. does + it want to be passed all the arguments of the block and nothing + else? """ + return not is_instancemethod(self.func) + + def _wants(self, theClass): + if is_instancemethod(self.func): + return self.func.im_class == theClass + else: + return False + + # treat the following methods in a special way when converting the + # Primitive to an AST + + @staticmethod + def make_tuple(*values): + """ This method corresponds to a Python tuple consisting of the given + values. """ + return tuple(values) + + @staticmethod + def controller_repeat(num): + """ Loop controller for the 'repeat' block """ + for i in range(num): + yield True + yield False + + @staticmethod + def controller_forever(): + """ Loop controller for the 'forever' block """ + while True: + yield True + + @staticmethod + def controller_while(boolean): + """ Loop controller for the 'while' block """ + while boolean: + yield True + yield False + + @staticmethod + def controller_until(boolean): + """ Loop controller for the 'until' block """ + while not boolean: + yield True + yield False + + LOOP_CONTROLLERS = [controller_repeat, controller_forever, + controller_while, controller_until] + + def _get_loop_controller(self): + """ Return the controller for this loop Primitive. Raise a + ValueError if no controller was found. """ + def _is_loop_controller(candidate): + return (callable(candidate) + and candidate in Primitive.LOOP_CONTROLLERS) + + # look at the first constant argument + first_const = self.constant_args.get(0, None) + if _is_loop_controller(first_const): + return first_const + + # look at the first slot wrapper + first_wrapper = self.slot_wrappers.get(0, None) + if _is_loop_controller(first_wrapper): + return first_wrapper + + # no controller found + raise ValueError("found no loop controller for " + repr(self)) + + @staticmethod + def do_nothing(): + pass + + @staticmethod + def group(prim_list): + """ Group together multiple Primitives into one. Treat each Primitive + as a separate line of code. """ + return_val = None + for prim in prim_list: + return_val = prim() + return return_val + + @staticmethod + def plus(arg1, arg2=None): + """ If only one argument is given, prefix it with '+'. If two + arguments are given, add the second to the first. """ + if arg2 is None: + return + arg1 + else: + return arg1 + arg2 + + @staticmethod + def minus(arg1, arg2=None): + """ If only one argument is given, change its sign. If two + arguments are given, subtract the second from the first. """ + if arg2 is None: + return - arg1 + else: + return arg1 - arg2 + + @staticmethod + def multiply(arg1, arg2): + """ Multiply the two arguments """ + return arg1 * arg2 + + @staticmethod + def divide(arg1, arg2): + """ Divide the first argument by the second """ + return arg1 / arg2 + + @staticmethod + def modulo(arg1, arg2): + """ Return the remainder of dividing the first argument by the second. + If the first argument is a string, format it with the value(s) in + the second argument. """ + return arg1 % arg2 + + @staticmethod + def power(arg1, arg2): + """ Raise the first argument to the power given by the second """ + return arg1 ** arg2 + + @staticmethod + def integer_division(arg1, arg2): + """ Divide the first argument by the second and return the integer + that is smaller than or equal to the result """ + return arg1 // arg2 + + @staticmethod + def bitwise_and(arg1, arg2): + """ Return the bitwise AND of the two arguments """ + return arg1 & arg2 + + @staticmethod + def bitwise_or(arg1, arg2): + """ Return the bitwise OR of the two arguments """ + return arg1 | arg2 + + @staticmethod + def and_(arg1, arg2): + """ Logcially conjoin the two arguments (using short-circuting) """ + return arg1 and arg2 + + @staticmethod + def or_(arg1, arg2): + """ Logically disjoin the two arguments (using short-circuting) """ + return arg1 or arg2 + + @staticmethod + def not_(arg): + """ Return True if the argument evaluates to False, and False + otherwise. """ + return not arg + + + +class PrimitiveCall(Primitive): + """ Primitive that is called when it is a constant argument to another + Primitive. """ + pass + + + +def is_instancemethod(method): + # TODO how to access the type `instancemethod` directly? + return type(method).__name__ == "instancemethod" + +def is_bound_instancemethod(method): + return is_instancemethod(method) and method.im_self is not None + +def is_unbound_instancemethod(method): + return is_instancemethod(method) and method.im_self is None + +def is_staticmethod(method): + # TODO how to access the type `staticmethod` directly? + return type(method).__name__ == "staticmethod" + + +def value_to_ast(value, *args_for_prim, **kwargs_for_prim): + """ Turn a value into an AST. Supported types: Primitive, int, float, + bool, basestring, list + If the value is already an AST, return it unchanged. + If the value is a non-exportable Primitive, return None. """ + # TODO media + if isinstance(value, ast.AST): + return value + elif isinstance(value, Primitive): + if value.export_me: + return value.get_ast(*args_for_prim, **kwargs_for_prim) + else: + return None + elif isinstance(value, bool): + return ast.Name(id=str(value), ctx=ast.Load) + elif isinstance(value, (int, float)): + return ast.Num(n=value) + elif isinstance(value, basestring): + return ast.Str(value) + elif isinstance(value, list): + ast_list = [] + for item in value: + item_ast = value_to_ast(item) + if item_ast is not None: + ast_list.append(item_ast) + return ast.List(elts=ast_list, ctx=ast.Load) + else: + raise ValueError("unknown type of raw value: " + repr(type(value))) + + +def call_me(something): + """ Return True iff this is a Primitive and its call_me attribute is + True, i.e. nothing is callable except for Primitives with + call_me == True """ + return isinstance(something, Primitive) and something.call_me + +def export_me(something): + """ Return True iff this is not a Primitive or its export_me attribute + is True, i.e. everything is exportable except for Primitives with + export_me == False """ + return not isinstance(something, Primitive) or something.export_me + + diff --git a/TurtleArt/taturtle.py b/TurtleArt/taturtle.py index ea1fb1a..16820f8 100644 --- a/TurtleArt/taturtle.py +++ b/TurtleArt/taturtle.py @@ -509,7 +509,8 @@ class Turtle: self._poly_points.append(('move', pos1[0], pos1[1])) self._poly_points.append(('line', pos2[0], pos2[1])) - def forward(self, distance, share=True): + def forward(self, distance, share=True, pendown=None): + ''' (The parameter `pendown` is ignored) ''' scaled_distance = distance * self._turtles.turtle_window.coord_scale old = self.get_xy() diff --git a/TurtleArt/tautils.py b/TurtleArt/tautils.py index 07b72d9..244be6d 100644 --- a/TurtleArt/tautils.py +++ b/TurtleArt/tautils.py @@ -311,7 +311,7 @@ def get_save_name(filefilter, load_save_folder, save_file_name): gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) - if filefilter in ['.png', '.svg', '.lg']: + if filefilter in ['.png', '.svg', '.lg', '.py']: suffix = filefilter else: suffix = SUFFIX[1] diff --git a/TurtleArt/tawindow.py b/TurtleArt/tawindow.py index e20a1cf..ef2c6c2 100644 --- a/TurtleArt/tawindow.py +++ b/TurtleArt/tawindow.py @@ -697,6 +697,9 @@ class TurtleArtWindow(): self.draw_overlay('Cartesian') return + def get_coord_scale(self): + return self.coord_scale + def set_polar(self, flag): ''' Turn on/off polar coordinates ''' self.draw_overlay('polar') diff --git a/plugins/turtle_blocks_extras/turtle_blocks_extras.py b/plugins/turtle_blocks_extras/turtle_blocks_extras.py index 48a2565..d6d4b29 100644 --- a/plugins/turtle_blocks_extras/turtle_blocks_extras.py +++ b/plugins/turtle_blocks_extras/turtle_blocks_extras.py @@ -38,6 +38,7 @@ from TurtleArt.tautils import (round_int, debug_output, get_path, data_to_string, find_group, image_to_base64, hat_on_top, listify, data_from_file) from TurtleArt.tajail import (myfunc, myfunc_import) +from TurtleArt.taprimitive import Primitive def _num_type(x): @@ -99,6 +100,11 @@ class Turtle_blocks_extras(Plugin): special_name=_('while'), help_string=_('do-while-True operator that uses \ boolean operators from Numbers palette')) + # Primitive is only used for exporting this block, not for running it + self.tw.lc.def_prim('while', 2, + Primitive(self.tw.lc.prim_loop, + slot_wrappers={0: Primitive(Primitive.controller_while)}), + True) # internally expanded macro palette.add_block('until', @@ -109,6 +115,11 @@ boolean operators from Numbers palette')) special_name=_('until'), help_string=_('do-until-True operator that uses \ boolean operators from Numbers palette')) + # Primitive is only used for exporting this block, not for running it + self.tw.lc.def_prim('until', 2, + Primitive(self.tw.lc.prim_loop, + slot_wrappers={0: Primitive(Primitive.controller_until)}), + True) primitive_dictionary['clamp'] = self._prim_clamp palette.add_block('sandwichclamp', diff --git a/pyexported/__init__.py b/pyexported/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pyexported/__init__.py diff --git a/pyexported/window_setup.py b/pyexported/window_setup.py new file mode 100644 index 0000000..5e9becf --- /dev/null +++ b/pyexported/window_setup.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python + +# TODO remove unused imports and global variables +import pygtk +pygtk.require('2.0') +import gtk +import gobject + +from gettext import gettext as _ + +try: + import gst + _GST_AVAILABLE = True +except ImportError: + # Turtle Art should not fail if gst is not available + _GST_AVAILABLE = False + +import os +import subprocess +import errno +from sys import argv + +from random import uniform +from math import atan2, pi +DEGTOR = 2 * pi / 360 + +import locale + +from TurtleArt.taconstants import (HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE, + MEDIA_SHAPES, STATUS_SHAPES, OVERLAY_SHAPES, + TOOLBAR_SHAPES, TAB_LAYER, RETURN, OVERLAY_LAYER, + CATEGORY_LAYER, BLOCKS_WITH_SKIN, ICON_SIZE, + PALETTE_SCALE, PALETTE_WIDTH, SKIN_PATHS, MACROS, + TOP_LAYER, BLOCK_LAYER, OLD_NAMES, DEFAULT_TURTLE, + TURTLE_LAYER, EXPANDABLE, NO_IMPORT, TEMPLATES, + PYTHON_SKIN, PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK, + EXPANDABLE_ARGS, XO1, XO15, XO175, XO30, XO4, TITLEXY, + CONTENT_ARGS, CONSTANTS, EXPAND_SKIN, PROTO_LAYER, + EXPANDABLE_FLOW, SUFFIX) +from TurtleArt.talogo import (LogoCode, primitive_dictionary, logoerror) +from TurtleArt.tacanvas import TurtleGraphics +from TurtleArt.tablock import (Blocks, Block) +from TurtleArt.taturtle import (Turtles, Turtle) +from TurtleArt.tautils import (magnitude, get_load_name, get_save_name, data_from_file, + data_to_file, round_int, get_id, get_pixbuf_from_journal, + movie_media_type, audio_media_type, image_media_type, + save_picture, calc_image_size, get_path, hide_button_hit, + show_button_hit, arithmetic_check, xy, + find_block_to_run, find_top_block, journal_check, + find_group, find_blk_below, data_to_string, + find_start_stack, get_hardware, debug_output, + error_output, convert, find_bot_block, + restore_clamp, collapse_clamp, data_from_string, + increment_name, get_screen_dpi) +from TurtleArt.tasprite_factory import (SVG, svg_str_to_pixbuf, svg_from_file) +from TurtleArt.sprites import (Sprites, Sprite) + +if _GST_AVAILABLE: + from TurtleArt.tagplay import stop_media + +import cairo + +from TurtleArt.tawindow import TurtleArtWindow + + +# path to the toplevel directory of the TA installation +_TA_INSTALLATION_PATH = None +# search the PYTHONPATH for a dir containing TurtleArt/tawindow.py +PYTHONPATH = os.environ["PYTHONPATH"] +for path in PYTHONPATH.split(":"): + try: + entries = os.listdir(path) + except OSError: + continue + if "TurtleArt" in entries: + new_path = os.path.join(path, "TurtleArt") + try: + new_entries = os.listdir(new_path) + except OSError: + continue + if "tawindow.py" in new_entries: + _TA_INSTALLATION_PATH = path + break +# if the TA installation path was not found, notify the user and refuse to run +if _TA_INSTALLATION_PATH is None: + print _("The path to the TurtleArt installation must be listed in the " + "environment variable PYTHONPATH.") + exit(1) + +_PLUGIN_SUBPATH = 'plugins' +_MACROS_SUBPATH = 'macros' + + + +class DummyTurtleMain(object): + """Keep the main objects for running a dummy TA window in one place. + (Try not to have to inherit from turtleblocks.TurtleMain.) + """ + + def __init__(self, win, name="exported project"): + """Create a scrolled window to contain the turtle canvas. + win -- a GTK toplevel window + """ + self.win = win + self.set_title = self.win.set_title + + # setup a scrolled container for the canvas + self.vbox = gtk.VBox(False, 0) + self.vbox.show() + self.sw = gtk.ScrolledWindow() + self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.sw.show() + self.canvas = gtk.DrawingArea() + width = gtk.gdk.screen_width() * 2 + height = gtk.gdk.screen_height() * 2 + self.canvas.set_size_request(width, height) + self.sw.add_with_viewport(self.canvas) + self.canvas.show() + self.vbox.pack_end(self.sw, True, True) + self.win.add(self.vbox) + self.win.show_all() + + # exported code is always in interactive mode + interactive = True + + # copied from turtleblocks.TurtleMain._build_window() + if interactive: + gdk_win = self.canvas.get_window() + cr = gdk_win.cairo_create() + surface = cr.get_target() + else: + img_surface = cairo.ImageSurface(cairo.FORMAT_RGB24, + 1024, 768) + cr = cairo.Context(img_surface) + surface = cr.get_target() + self.turtle_canvas = surface.create_similar( + cairo.CONTENT_COLOR, max(1024, gtk.gdk.screen_width() * 2), + max(768, gtk.gdk.screen_height() * 2)) + + + + # instantiate an instance of a dummy sub-class that supports only + # the stuff TurtleGraphics needs + # TODO don't hardcode running_sugar + self.tw = TurtleArtWindow(self.canvas, _TA_INSTALLATION_PATH, + turtle_canvas=self.turtle_canvas, + parent=self, running_sugar=False, + running_turtleart=False) + + self.name = name + + + def _quit_ta(self, widget=None, e=None): + """Quit all plugins and the main window. No need to prompt the user + to save their work, since they cannot change anything. + """ + for plugin in self.tw.turtleart_plugins: + if hasattr(plugin, 'quit'): + plugin.quit() + gtk.main_quit() + exit() + + + +def get_tw(): + """ Create a GTK window and instantiate a DummyTurtleMain instance. Return + the TurtleArtWindow object that holds the turtles and the canvas. + """ + # copied from turtleblocks.TurtleMain._setup_gtk() + + win = gtk.Window(gtk.WINDOW_TOPLEVEL) + gui = DummyTurtleMain(win=win, name=argv[0]) + # TODO re-enable this code (after giving gui the right attributes) + # win.set_default_size(gui.width, gui.height) + # win.move(gui.x, gui.y) + win.maximize() + # win.set_title('%s %s' % (gui.name, str(gui.version))) + # if os.path.exists(os.path.join(gui._execdirname, gui._ICON_SUBPATH)): + # win.set_icon_from_file(os.path.join(gui._execdirname, + # gui._ICON_SUBPATH)) + win.show() + win.connect('delete_event', gui._quit_ta) + + return gui.tw + + diff --git a/turtleblocks.py b/turtleblocks.py index 18bc1ac..368c14d 100755 --- a/turtleblocks.py +++ b/turtleblocks.py @@ -53,9 +53,11 @@ from gettext import gettext as _ from TurtleArt.taconstants import (OVERLAY_LAYER, DEFAULT_TURTLE_COLORS, TAB_LAYER, SUFFIX) +from TurtleArt.taprimitive import PyExportError from TurtleArt.tautils import (data_from_string, get_save_name) from TurtleArt.tawindow import TurtleArtWindow from TurtleArt.taexportlogo import save_logo +from TurtleArt.taexportpython import save_python from util.menubuilder import MenuBuilder @@ -385,6 +387,8 @@ return %s(self)" % (p, P, P) self._do_save_picture_cb) MenuBuilder.make_menu_item(menu, _('Save as Logo'), self._do_save_logo_cb) + MenuBuilder.make_menu_item(menu, _('Save as Python'), + self._do_save_python_cb) MenuBuilder.make_menu_item(menu, _('Quit'), self._quit_ta) activity_menu = MenuBuilder.make_sub_menu(menu, _('File')) @@ -535,6 +539,34 @@ Would you like to save before quitting?')) f.write(logocode) f.close() + def _do_save_python_cb(self, widget): + ''' Callback for saving the project as Python code. ''' + # catch PyExportError and display a user-friendly message instead + try: + pythoncode = save_python(self.tw) + except PyExportError as pyee: + if pyee.block is not None: + pyee.block.highlight() + self.tw.showlabel('status', str(pyee)) + return + if not pythoncode: + return + # use name of TA project if it has been saved already + default_name = self.tw.save_file_name + if default_name is None: + default_name = _("myproject") + elif default_name.endswith(".ta") or default_name.endswith(".tb"): + default_name = default_name[:-3] + save_type = '.py' + (filename, self.tw.load_save_folder) = get_save_name( + save_type, self.tw.load_save_folder, default_name) + if isinstance(filename, unicode): + filename = filename.encode('ascii', 'replace') + if filename is not None: + f = file(filename, 'w') + f.write(pythoncode) + f.close() + def _do_resize_cb(self, widget, factor): ''' Callback to resize blocks. ''' if factor == -1: diff --git a/util/codegen.py b/util/codegen.py new file mode 100644 index 0000000..c532d3b --- /dev/null +++ b/util/codegen.py @@ -0,0 +1,571 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2008, Armin Ronacher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +""" + codegen + ~~~~~~~ + + Extension to ast that allow ast -> python code generation. + + :copyright: Copyright 2008 by Armin Ronacher. + :license: BSD. + + Modified by Marion Zepf. +""" +from ast import * + + +def to_source(node, indent_with=' ' * 4, add_line_information=False): + """This function can convert a node tree back into python sourcecode. + This is useful for debugging purposes, especially if you're dealing with + custom asts not generated by python itself. + + It could be that the sourcecode is evaluable when the AST itself is not + compilable / evaluable. The reason for this is that the AST contains some + more data than regular sourcecode does, which is dropped during + conversion. + + Each level of indentation is replaced with `indent_with`. Per default this + parameter is equal to four spaces as suggested by PEP 8, but it might be + adjusted to match the application's styleguide. + + If `add_line_information` is set to `True` comments for the line numbers + of the nodes are added to the output. This can be used to spot wrong line + number information of statement nodes. + """ + generator = SourceGenerator(indent_with, add_line_information) + generator.visit(node) + return ''.join(generator.result) + + +class SourceGenerator(NodeVisitor): + """This visitor is able to transform a well formed syntax tree into python + sourcecode. For more details have a look at the docstring of the + `node_to_source` function. + """ + + UNARYOP_SYMBOLS = {Invert: "~", Not: "not", UAdd: "+", USub: "-"} + # TODO avoid turning (-1)**2 into -1**2 + BINOP_SYMBOLS = {Add: "+", Sub: "-", Mult: "*", Div: "/", Mod: "%", + LShift: "<<", RShift:">>", BitOr: "|", BitXor: "^", + BitAnd: "&", FloorDiv: "//", Pow: "**"} + BOOLOP_SYMBOLS = {And: "and", Or: "or"} + CMPOP_SYMBOLS = {Eq: "==", NotEq: "!=", Lt: "<", LtE: "<=", Gt: ">", + GtE: ">=", Is: "is", IsNot: "is not", In: "in", + NotIn: "not in"} + + def __init__(self, indent_with, add_line_information=False): + self.result = [] + self.indent_with = indent_with + self.add_line_information = add_line_information + self.indentation = 0 + self.new_lines = 0 + + def write(self, x): + if self.new_lines: + if self.result: + self.result.append('\n' * self.new_lines) + self.result.append(self.indent_with * self.indentation) + self.new_lines = 0 + self.result.append(x) + + def newline(self, node=None, extra=0): + self.new_lines = max(self.new_lines, 1 + extra) + if node is not None and self.add_line_information: + self.write('# line: %s' % node.lineno) + self.new_lines = 1 + + def body(self, statements, do_indent=True): + if do_indent: + self.indentation += 1 + for stmt in statements: + self.newline() + self.visit(stmt) + if do_indent: + self.indentation -= 1 + + def body_or_else(self, node): + self.body(node.body) + if node.orelse: + self.newline() + self.write('else:') + self.body(node.orelse) + + def signature(self, node): + want_comma = [] + def write_comma(): + if want_comma: + self.write(', ') + else: + want_comma.append(True) + + padding = [None] * (len(node.args) - len(node.defaults)) + for arg, default in zip(node.args, padding + node.defaults): + write_comma() + self.visit(arg) + if default is not None: + self.write('=') + self.visit(default) + if node.vararg is not None: + write_comma() + self.write('*' + node.vararg) + if node.kwarg is not None: + write_comma() + self.write('**' + node.kwarg) + + def decorators(self, node): + for decorator in node.decorator_list: + self.newline(decorator) + self.write('@') + self.visit(decorator) + + # Statements + + def visit_Assign(self, node): + self.newline(node) + for idx, target in enumerate(node.targets): + if idx: + self.write(', ') + self.visit(target) + self.write(' = ') + self.visit(node.value) + + def visit_AugAssign(self, node): + self.newline(node) + self.visit(node.target) + self.write(self.BINOP_SYMBOLS[node.op] + '=') + self.visit(node.value) + + def visit_ImportFrom(self, node): + self.newline(node) + self.write('from %s%s import ' % ('.' * node.level, node.module)) + for idx, item in enumerate(node.names): + if idx: + self.write(', ') + self.visit(item) + + def visit_Import(self, node): + self.newline(node) + for item in node.names: + self.write('import ') + self.visit(item) + + def visit_Expr(self, node): + self.newline(node) + self.generic_visit(node) + + def visit_Module(self, node): + self.body(node.body, do_indent=False) + + def visit_FunctionDef(self, node): + self.newline(extra=1) + self.decorators(node) + self.newline(node) + self.write('def %s(' % node.name) + self.signature(node.args) + self.write('):') + self.body(node.body) + + def visit_ClassDef(self, node): + have_args = [] + def paren_or_comma(): + if have_args: + self.write(', ') + else: + have_args.append(True) + self.write('(') + + self.newline(extra=2) + self.decorators(node) + self.newline(node) + self.write('class %s' % node.name) + for base in node.bases: + paren_or_comma() + self.visit(base) + # XXX: the if here is used to keep this module compatible + # with python 2.6. + if hasattr(node, 'keywords'): + for keyword in node.keywords: + paren_or_comma() + self.write(keyword.arg + '=') + self.visit(keyword.value) + if node.starargs is not None: + paren_or_comma() + self.write('*') + self.visit(node.starargs) + if node.kwargs is not None: + paren_or_comma() + self.write('**') + self.visit(node.kwargs) + # TODO wtf??? + self.write(have_args and '):' or ':') + self.body(node.body) + + def visit_If(self, node): + self.newline(node) + self.write('if ') + self.visit(node.test) + self.write(':') + self.body(node.body) + while True: + else_ = node.orelse + if len(else_) == 1 and isinstance(else_[0], If): + node = else_[0] + self.newline() + self.write('elif ') + self.visit(node.test) + self.write(':') + self.body(node.body) + elif else_: + self.newline() + self.write('else:') + self.body(else_) + break + else: + break + + def visit_For(self, node): + self.newline(node) + self.write('for ') + self.visit(node.target) + self.write(' in ') + self.visit(node.iter) + self.write(':') + self.body_or_else(node) + + def visit_While(self, node): + self.newline(node) + self.write('while ') + self.visit(node.test) + self.write(':') + self.body_or_else(node) + + def visit_With(self, node): + self.newline(node) + self.write('with ') + self.visit(node.context_expr) + if node.optional_vars is not None: + self.write(' as ') + self.visit(node.optional_vars) + self.write(':') + self.body(node.body) + + def visit_Pass(self, node): + self.newline(node) + self.write('pass') + + def visit_Print(self, node): + # XXX: python 2.6 only + self.newline(node) + self.write('print ') + want_comma = False + if node.dest is not None: + self.write(' >> ') + self.visit(node.dest) + want_comma = True + for value in node.values: + if want_comma: + self.write(', ') + self.visit(value) + want_comma = True + if not node.nl: + self.write(',') + + def visit_Delete(self, node): + self.newline(node) + self.write('del ') + for idx, target in enumerate(node): + if idx: + self.write(', ') + self.visit(target) + + def visit_TryExcept(self, node): + self.newline(node) + self.write('try:') + self.body(node.body) + for handler in node.handlers: + self.visit(handler) + + def visit_TryFinally(self, node): + self.newline(node) + self.write('try:') + self.body(node.body) + self.newline(node) + self.write('finally:') + self.body(node.finalbody) + + def visit_Global(self, node): + self.newline(node) + self.write('global ' + ', '.join(node.names)) + + def visit_Nonlocal(self, node): + self.newline(node) + self.write('nonlocal ' + ', '.join(node.names)) + + def visit_Return(self, node): + self.newline(node) + self.write('return ') + self.visit(node.value) + + def visit_Break(self, node): + self.newline(node) + self.write('break') + + def visit_Continue(self, node): + self.newline(node) + self.write('continue') + + def visit_Raise(self, node): + # XXX: Python 2.6 / 3.0 compatibility + self.newline(node) + self.write('raise') + if hasattr(node, 'exc') and node.exc is not None: + self.write(' ') + self.visit(node.exc) + if node.cause is not None: + self.write(' from ') + self.visit(node.cause) + elif hasattr(node, 'type') and node.type is not None: + self.visit(node.type) + if node.inst is not None: + self.write(', ') + self.visit(node.inst) + if node.tback is not None: + self.write(', ') + self.visit(node.tback) + + # Expressions + + def visit_Attribute(self, node): + self.visit(node.value) + self.write('.' + node.attr) + + def visit_Call(self, node): + want_comma = [] + def write_comma(): + if want_comma: + self.write(', ') + else: + want_comma.append(True) + + self.visit(node.func) + self.write('(') + for arg in node.args: + write_comma() + self.visit(arg) + for keyword in node.keywords: + write_comma() + self.write(keyword.arg + '=') + self.visit(keyword.value) + if node.starargs is not None: + write_comma() + self.write('*') + self.visit(node.starargs) + if node.kwargs is not None: + write_comma() + self.write('**') + self.visit(node.kwargs) + self.write(')') + + def visit_Name(self, node): + self.write(node.id) + + def visit_Str(self, node): + self.write(repr(node.s)) + + def visit_Bytes(self, node): + self.write(repr(node.s)) + + def visit_Num(self, node): + self.write(repr(node.n)) + + def visit_Tuple(self, node): + self.write('(') + idx = -1 + for idx, item in enumerate(node.elts): + if idx: + self.write(', ') + self.visit(item) + # TODO wtf??? + self.write(idx and ')' or ',)') + + def sequence_visit(left, right): + def visit(self, node): + self.write(left) + for idx, item in enumerate(node.elts): + if idx: + self.write(', ') + self.visit(item) + self.write(right) + return visit + + visit_List = sequence_visit('[', ']') + visit_Set = sequence_visit('{', '}') + del sequence_visit + + def visit_Dict(self, node): + self.write('{') + for idx, (key, value) in enumerate(zip(node.keys, node.values)): + if idx: + self.write(', ') + self.visit(key) + self.write(': ') + self.visit(value) + self.write('}') + + def visit_BinOp(self, node): + self.visit(node.left) + self.write(' %s ' % self.BINOP_SYMBOLS[node.op]) + self.visit(node.right) + + def visit_BoolOp(self, node): + self.write('(') + for idx, value in enumerate(node.values): + if idx: + self.write(' %s ' % self.BOOLOP_SYMBOLS[node.op]) + self.visit(value) + self.write(')') + + def visit_Compare(self, node): + self.write('(') + self.write(node.left) + for op, right in zip(node.ops, node.comparators): + self.write(' %s %%' % self.CMPOP_SYMBOLS[op]) + self.visit(right) + self.write(')') + + def visit_UnaryOp(self, node): + self.write('(') + op = self.UNARYOP_SYMBOLS[node.op] + self.write(op) + if op == 'not': + self.write(' ') + self.visit(node.operand) + self.write(')') + + def visit_Subscript(self, node): + self.visit(node.value) + self.write('[') + self.visit(node.slice) + self.write(']') + + def visit_Slice(self, node): + if node.lower is not None: + self.visit(node.lower) + self.write(':') + if node.upper is not None: + self.visit(node.upper) + if node.step is not None: + self.write(':') + if not (isinstance(node.step, Name) and node.step.id == 'None'): + self.visit(node.step) + + def visit_ExtSlice(self, node): + for idx, item in node.dims: + if idx: + self.write(', ') + self.visit(item) + + def visit_Yield(self, node): + self.write('yield ') + self.visit(node.value) + + def visit_Lambda(self, node): + self.write('lambda ') + self.signature(node.args) + self.write(': ') + self.visit(node.body) + + def visit_Ellipsis(self, node): + self.write('Ellipsis') + + def generator_visit(left, right): + def visit(self, node): + self.write(left) + self.visit(node.elt) + for comprehension in node.generators: + self.visit(comprehension) + self.write(right) + return visit + + visit_ListComp = generator_visit('[', ']') + visit_GeneratorExp = generator_visit('(', ')') + visit_SetComp = generator_visit('{', '}') + del generator_visit + + def visit_DictComp(self, node): + self.write('{') + self.visit(node.key) + self.write(': ') + self.visit(node.value) + for comprehension in node.generators: + self.visit(comprehension) + self.write('}') + + def visit_IfExp(self, node): + self.visit(node.body) + self.write(' if ') + self.visit(node.test) + self.write(' else ') + self.visit(node.orelse) + + def visit_Starred(self, node): + self.write('*') + self.visit(node.value) + + def visit_Repr(self, node): + # XXX: python 2.6 only + self.write('`') + self.visit(node.value) + self.write('`') + + # Helper Nodes + + def visit_alias(self, node): + self.write(node.name) + if node.asname is not None: + self.write(' as ' + node.asname) + + def visit_comprehension(self, node): + self.write(' for ') + self.visit(node.target) + self.write(' in ') + self.visit(node.iter) + if node.ifs: + for if_ in node.ifs: + self.write(' if ') + self.visit(if_) + + def visit_excepthandler(self, node): + self.newline(node) + self.write('except') + if node.type is not None: + self.write(' ') + self.visit(node.type) + if node.name is not None: + self.write(' as ') + self.visit(node.name) + self.write(':') + self.body(node.body) |