diff options
-rw-r--r-- | TurtleArt/tabasics.py | 967 | ||||
-rw-r--r-- | TurtleArt/tablock.py | 86 | ||||
-rw-r--r-- | TurtleArt/tacanvas.py | 20 | ||||
-rw-r--r-- | TurtleArt/taconstants.py | 108 | ||||
-rw-r--r-- | TurtleArt/taexportlogo.py | 14 | ||||
-rw-r--r-- | TurtleArt/taexportpython.py | 259 | ||||
-rw-r--r-- | TurtleArt/tajail.py | 20 | ||||
-rw-r--r-- | TurtleArt/talogo.py | 495 | ||||
-rw-r--r-- | TurtleArt/taprimitive.py | 1105 | ||||
-rw-r--r-- | TurtleArt/taturtle.py | 57 | ||||
-rw-r--r-- | TurtleArt/tatype.py | 439 | ||||
-rw-r--r-- | TurtleArt/tautils.py | 32 | ||||
-rw-r--r-- | TurtleArt/tawindow.py | 122 | ||||
-rw-r--r-- | TurtleArtActivity.py | 45 | ||||
-rw-r--r-- | doc/primitives-with-arguments.md | 125 | ||||
-rw-r--r-- | doc/type-system.md | 116 | ||||
-rw-r--r-- | icons/python-saveoff.svg | 111 | ||||
-rw-r--r-- | icons/python-saveon.svg | 111 | ||||
-rw-r--r-- | plugins/camera_sensor/camera_sensor.py | 5 | ||||
-rw-r--r-- | plugins/turtle_blocks_extras/turtle_blocks_extras.py | 344 | ||||
-rw-r--r-- | pyexported/__init__.py | 0 | ||||
-rw-r--r-- | pyexported/window_setup.py | 138 | ||||
-rw-r--r-- | samples/game-set.ta | 582 | ||||
-rwxr-xr-x | turtleblocks.py | 32 | ||||
-rw-r--r-- | util/ast_extensions.py | 52 | ||||
-rw-r--r-- | util/codegen.py | 593 |
26 files changed, 4600 insertions, 1378 deletions
diff --git a/TurtleArt/tabasics.py b/TurtleArt/tabasics.py index beccd2a..f450f8b 100644 --- a/TurtleArt/tabasics.py +++ b/TurtleArt/tabasics.py @@ -20,72 +20,94 @@ #THE SOFTWARE. ''' -This file contains the constants that by-in-large determine the -behavior of Turtle Art. Notably, the block palettes are defined -below. If you want to add a new block to Turtle Art, you could -simply add a block of code to this file or to turtle_block_plugin.py, -which contains additional blocks. (Even better, write your own plugin!!) +This file contains the constants that by-in-large determine the +behavior of Turtle Art. Notably, the block palettes are defined +below. If you want to add a new block to Turtle Art, you could +simply add a block of code to this file or to +../plugins/turtle_blocks_extras/turtle_blocks_extras.py , +which contains additional blocks. (Even better, write your own +plugin!!) Adding a new palette is simply a matter of: + palette = make_palette('mypalette', # the name of your palette colors=["#00FF00", "#00A000"], help_string=_('Palette of my custom commands')) -For example, if we want to add a new turtle command, 'uturn', we'd use the -add_block method in the Palette class. +For example, if we want to add a new turtle command, 'uturn', +we'd use the `add_block` method in the Palette class. + palette.add_block('uturn', # the name of your block style='basic-style', # the block style label=_('u turn'), # the label for the block prim_name='uturn', # code reference (see below) help_string=_('turns the turtle 180 degrees')) - # Next, you need to define what your block will do: - # def_prim takes 3 arguments: the primitive name, the number of - # arguments -- 0 in this case -- and the function to call -- in this - # case, we define the _prim_uturn function to set heading += 180. - self.tw.lc.def_prim('uturn', 0, lambda self: self._prim_uturn) - def _prim_uturn(self): - value = self.tw.turtles.get_active_turtle().get_heading() + 180 - self.tw.turtles.get_active_turtle().set_heading(value) +Next, you need to define what your block will do: def_prim takes +3 arguments: the primitive name, the number of arguments --- 0 +in this case --- and a Primitive object. A Primitive object +represents the statement to be executed when the block is +executed in Turtle Art. For the 'uturn' block, we would like the +statement to look roughly like this: + + Turtle.set_heading(plus(Turtle.get_heading(), 180)) + +Formally, a Primitive object consists of a function, its return +type, and descriptions of its arguments and keyword arguments. +The return type is not a Python type, but a type from Turtle +Art's internal type system. All available types are defined as +constants in tatype.py . + +In this case, we know in advance which arguments each function +gets, so we can use ConstantArg objects as argument descrip- +tions. (For examples where the arguments come from other blocks, +please refer to ../doc/primitives-with-arguments.md .) Note that +Primitive objects can be arguments to other Primitive objects. +This leads to the following tree-like structure for our 'uturn' +block: + + prim_uturn = Primitive(Turtle.set_heading, + arg_descs=[ConstantArg(Primitive( + Primitive.plus, return_type=TYPE_NUMBER, + arg_descs=[ConstantArg(Primitive( + Turtle.get_heading, return_type=TYPE_NUMBER)), + ConstantArg(180)]))], + call_afterwards=self.after_uturn) + + self.tw.lc.def_prim('uturn', 0, prim_uturn) + + # somewhere else in the same class: + def after_uturn(self, value): if self.tw.lc.update_values: self.tw.lc.update_label_value('heading', value) -That's it. When you next run Turtle Art, you will have a 'uturn' block -on the 'mypalette' palette. +The `call_afterwards` attribute is a simple function that is +called just after executing the block. It is often used for +updating GUI labels. -You will have to create icons for the palette-selector buttons. These -are kept in the icons subdirectory. You need two icons: -mypaletteoff.svg and mypaletteon.svg, where 'mypalette' is the same -string as the entry you used in instantiating the Palette class. Note -that the icons should be the same size (55x55) as the others. (This is -the default icon size for Sugar toolbars.) -''' +That's it. When you next run Turtle Art, you will have a 'uturn' +block on the 'mypalette' palette. -from time import time, sleep -from math import sqrt -from random import uniform +You will have to create icons for the palette-selector buttons. +These are kept in the 'icons' subdirectory. You need two icons: +mypaletteoff.svg and mypaletteon.svg, where 'mypalette' is the +same string as the entry you used in instantiating the Palette +object. Note that the icons should be the same size (55x55) as +the others. (This is the default icon size for Sugar toolbars.) +''' +from time import time from gettext import gettext as _ 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 (COLORDICT, CONSTANTS) - - -def _color_to_num(c): - if COLORDICT[c][0] is None: - return(COLORDICT[c][1]) - else: - return(COLORDICT[c][0]) - - -def _num_type(x): - ''' Is x a number type? ''' - if isinstance(x, (int, float)): - return True - return False +from talogo import primitive_dictionary +from taconstants import (Color, CONSTANTS) +from taprimitive import (ArgSlot, ConstantArg, or_, Primitive) +from tatype import (TYPE_BOOL, TYPE_BOX, TYPE_CHAR, TYPE_COLOR, TYPE_FLOAT, + TYPE_INT, TYPE_NUMBER, TYPE_NUMERIC_STRING, TYPE_OBJECT, + TYPE_STRING) +from taturtle import Turtle def _millisecond(): @@ -99,6 +121,15 @@ class Palettes(): def __init__(self, turtle_window): self.tw = turtle_window + self.prim_cache = { + "minus": Primitive(Primitive.minus, + return_type=TYPE_NUMBER, + arg_descs=[ArgSlot(TYPE_NUMBER)]), + "ord": Primitive(ord, + return_type=TYPE_INT, + arg_descs=[ArgSlot(TYPE_CHAR)]) + } # avoid several Primitives of the same function + self._turtle_palette() self._pen_palette() @@ -123,7 +154,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'), @@ -134,8 +164,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, + arg_descs=[ArgSlot(TYPE_NUMBER)], + call_afterwards=self.after_move)) palette.add_block('back', style='basic-style-1arg', @@ -145,12 +176,11 @@ 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, + arg_descs=[ArgSlot(TYPE_NUMBER, + wrapper=self.prim_cache["minus"])], + call_afterwards=self.after_move)) - primitive_dictionary['clean'] = self._prim_clear palette.add_block('clean', style='basic-style-extended-vertical', label=_('clean'), @@ -158,12 +188,17 @@ class Palettes(): logo_command='clean', help_string=_('clears the screen and reset the \ turtle')) - self.tw.lc.def_prim( - 'clean', - 0, - lambda self: primitive_dictionary['clean']()) + self.tw.lc.def_prim('clean', 0, + Primitive(Primitive.group, arg_descs=[ConstantArg([ + Primitive(self.tw.clear_plugins), + Primitive(self.tw.lc.stop_playing_media), + Primitive(self.tw.lc.reset_scale), + Primitive(self.tw.lc.reset_timer), + Primitive(self.tw.lc.clear_value_blocks), + Primitive(self.tw.lc.reset_internals), + Primitive(self.tw.canvas.clearscreen), + Primitive(self.tw.turtles.reset_turtles)])])) - primitive_dictionary['right'] = self._prim_right palette.add_block('left', style='basic-style-1arg', label=_('left'), @@ -173,8 +208,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, + arg_descs=[ArgSlot(TYPE_NUMBER, + wrapper=self.prim_cache["minus"])], + call_afterwards=self.after_right)) palette.add_block('right', style='basic-style-1arg', @@ -187,9 +225,10 @@ degrees)')) self.tw.lc.def_prim( 'right', 1, - lambda self, x: primitive_dictionary['right'](x)) + Primitive(Turtle.right, + arg_descs=[ArgSlot(TYPE_NUMBER)], + call_afterwards=self.after_right)) - primitive_dictionary['arc'] = self._prim_arc palette.add_block('arc', style='basic-style-2arg', label=[_('arc'), _('angle'), _('radius')], @@ -197,11 +236,11 @@ degrees)')) default=[90, 100], logo_command='taarc', help_string=_('moves turtle along an arc')) - self.tw.lc.def_prim( - 'arc', - 2, - lambda self, x, y: primitive_dictionary['arc']( - self.tw.turtles.get_active_turtle().arc, x, y)) + self.tw.lc.def_prim('arc', 2, + Primitive(Turtle.arc, + arg_descs=[ArgSlot(TYPE_NUMBER), + ArgSlot(TYPE_NUMBER)], + call_afterwards=self.after_arc)) define_logo_function('taarc', 'to taarc :a :r\nrepeat round :a \ [right 1 forward (0.0175 * :r)]\nend\n') @@ -213,14 +252,12 @@ degrees)')) default=[0, 0], help_string=_('moves turtle to position xcor, ycor; \ (0, 0) is in the center of the screen.')) - self.tw.lc.def_prim( - 'setxy2', - 2, - lambda self, x, y: primitive_dictionary['move']( - self.tw.turtles.get_active_turtle().set_xy, x, y)) + self.tw.lc.def_prim('setxy2', 2, + Primitive(Turtle.set_xy, + arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)], + call_afterwards=self.after_move)) define_logo_function('tasetxy', 'to tasetxy :x :y\nsetxy :x :y\nend\n') - primitive_dictionary['set'] = self._prim_set palette.add_block('seth', style='basic-style-1arg', label=_('set heading'), @@ -229,11 +266,10 @@ degrees)')) logo_command='seth', help_string=_('sets the heading of the turtle (0 is \ 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)) + self.tw.lc.def_prim('seth', 1, + Primitive(Turtle.set_heading, + arg_descs=[ArgSlot(TYPE_NUMBER)], + call_afterwards=lambda value: self.after_set('heading',value))) palette.add_block('xcor', style='box-style', @@ -243,11 +279,10 @@ the turtle (can be used in place of a number block)'), value_block=True, prim_name='xcor', logo_command='xcor') - self.tw.lc.def_prim( - 'xcor', - 0, - lambda self: self.tw.turtles.get_active_turtle().get_xy()[0] / - self.tw.coord_scale) + self.tw.lc.def_prim('xcor', 0, + Primitive(Primitive.divide, return_type=TYPE_FLOAT, + arg_descs=[ConstantArg(Primitive(Turtle.get_x)), + ConstantArg(Primitive(self.tw.get_coord_scale))])) palette.add_block('ycor', style='box-style', @@ -257,11 +292,10 @@ the turtle (can be used in place of a number block)'), value_block=True, prim_name='ycor', logo_command='ycor') - self.tw.lc.def_prim( - 'ycor', - 0, - lambda self: self.tw.turtles.get_active_turtle().get_xy()[1] / - self.tw.coord_scale) + self.tw.lc.def_prim('ycor', 0, + Primitive(Primitive.divide, return_type=TYPE_FLOAT, + arg_descs=[ConstantArg(Primitive(Turtle.get_y)), + ConstantArg(Primitive(self.tw.get_coord_scale))])) palette.add_block('heading', style='box-style', @@ -271,10 +305,8 @@ 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, return_type=TYPE_NUMBER)) palette.add_block('turtle-label', hidden=True, @@ -291,12 +323,11 @@ turtle (can be used in place of a number block)'), logo_command='tasetxypenup', help_string=_('moves turtle to position xcor, ycor; \ (0, 0) is in the center of the screen.')) - self.tw.lc.def_prim( - 'setxy', - 2, - lambda self, x, y: primitive_dictionary['move']( - self.tw.turtles.get_active_turtle().set_xy, x, y, - pendown=False)) + self.tw.lc.def_prim('setxy', 2, + Primitive(Turtle.set_xy, + arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)], + kwarg_descs={'pendown': ConstantArg(False)}, + call_afterwards=self.after_move)) define_logo_function('tasetxypenup', 'to tasetxypenup :x :y\npenup\n\ setxy :x :y\npendown\nend\n') @@ -316,10 +347,9 @@ setxy :x :y\npendown\nend\n') logo_command='tasetbackground', help_string=_('fills the background with (color, \ shade)')) - self.tw.lc.def_prim( - 'fillscreen', - 2, - lambda self, x, y: self.tw.canvas.fillscreen(x, y)) + self.tw.lc.def_prim('fillscreen', 2, + Primitive(self.tw.canvas.fillscreen, + arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)])) palette.add_block('fillscreen2', style='basic-style-3arg', @@ -330,10 +360,10 @@ shade)')) logo_command='tasetbackground', help_string=_('fills the background with (color, \ shade)')) - self.tw.lc.def_prim( - 'fillscreen2', - 3, - lambda self, x, y, z: self.tw.canvas.fillscreen_with_gray(x, y, z)) + self.tw.lc.def_prim('fillscreen2', 3, + Primitive(self.tw.canvas.fillscreen_with_gray, + arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER), + ArgSlot(TYPE_NUMBER)])) define_logo_function('tasetbackground', 'to tasetbackground :color \ :shade\ntasetshade :shade\nsetbackground :color\nend\n') @@ -346,11 +376,10 @@ shade)')) logo_command='tasetpencolor', help_string=_('sets color of the line drawn by the \ turtle')) - self.tw.lc.def_prim( - 'setcolor', - 1, - lambda self, x: primitive_dictionary['set']( - 'color', self.tw.turtles.get_active_turtle().set_color, x)) + self.tw.lc.def_prim('setcolor', 1, + Primitive(Turtle.set_color, + arg_descs=[or_(ArgSlot(TYPE_COLOR), ArgSlot(TYPE_NUMBER))], + call_afterwards=lambda value: self.after_set('color', value))) palette.add_block('setshade', style='basic-style-1arg', @@ -360,11 +389,10 @@ turtle')) logo_command='tasetshade', help_string=_('sets shade of the line drawn by the \ turtle')) - self.tw.lc.def_prim( - 'setshade', - 1, - lambda self, x: primitive_dictionary['set']( - 'shade', self.tw.turtles.get_active_turtle().set_shade, x)) + self.tw.lc.def_prim('setshade', 1, + Primitive(Turtle.set_shade, + arg_descs=[ArgSlot(TYPE_NUMBER)], + call_afterwards=lambda value: self.after_set('shade', value))) palette.add_block('setgray', style='basic-style-1arg', @@ -373,11 +401,10 @@ turtle')) default=100, help_string=_('sets gray level of the line drawn by \ 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)) + self.tw.lc.def_prim('setgray', 1, + Primitive(Turtle.set_gray, + arg_descs=[ArgSlot(TYPE_NUMBER)], + call_afterwards=lambda value: self.after_set('gray', value))) palette.add_block('color', style='box-style', @@ -387,10 +414,8 @@ 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, return_type=TYPE_NUMBER)) palette.add_block('shade', style='box-style', @@ -399,10 +424,8 @@ 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, return_type=TYPE_NUMBER)) palette.add_block('gray', style='box-style', @@ -411,8 +434,8 @@ 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, return_type=TYPE_NUMBER)) palette.add_block('penup', style='basic-style-extended-vertical', @@ -420,11 +443,8 @@ used in place of a number block)'), prim_name='penup', logo_command='penup', help_string=_('Turtle will not draw when moved.')) - self.tw.lc.def_prim( - 'penup', - 0, - lambda self: - self.tw.turtles.get_active_turtle().set_pen_state(False)) + self.tw.lc.def_prim('penup', 0, + Primitive(Turtle.set_pen_state, arg_descs=[ConstantArg(False)])) palette.add_block('pendown', style='basic-style-extended-vertical', @@ -432,21 +452,16 @@ used in place of a number block)'), prim_name='pendown', logo_command='pendown', help_string=_('Turtle will draw when moved.')) - self.tw.lc.def_prim( - 'pendown', - 0, - lambda self: - self.tw.turtles.get_active_turtle().set_pen_state(True)) + self.tw.lc.def_prim('pendown', 0, + Primitive(Turtle.set_pen_state, arg_descs=[ConstantArg(True)])) palette.add_block('penstate', style='boolean-block-style', label=_('pen down?'), prim_name='penstate', help_string=_('returns True if pen is down')) - self.tw.lc.def_prim( - 'penstate', - 0, - lambda self: self.tw.turtles.get_active_turtle().get_pen_state()) + self.tw.lc.def_prim('penstate', 0, + Primitive(Turtle.get_pen_state, return_type=TYPE_BOOL)) palette.add_block('setpensize', style='basic-style-1arg', @@ -456,10 +471,10 @@ used in place of a number block)'), logo_command='setpensize', help_string=_('sets size of the line drawn by the \ 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)) + self.tw.lc.def_prim('setpensize', 1, + Primitive(Turtle.set_pen_size, + arg_descs=[ArgSlot(TYPE_NUMBER)], + call_afterwards=lambda val: self.after_set('pensize', val))) define_logo_function('tasetpensize', 'to tasetpensize :a\nsetpensize round :a\nend\n') @@ -469,10 +484,8 @@ turtle')) prim_name='startfill', help_string=_('starts filled polygon (used with end \ fill block)')) - self.tw.lc.def_prim( - 'startfill', - 0, - lambda self: self.tw.turtles.get_active_turtle().start_fill()) + self.tw.lc.def_prim('startfill', 0, + Primitive(Turtle.start_fill)) palette.add_block('stopfill', style='basic-style-extended-vertical', @@ -480,10 +493,8 @@ fill block)')) prim_name='stopfill', help_string=_('completes filled polygon (used with \ start fill block)')) - self.tw.lc.def_prim( - 'stopfill', - 0, - lambda self: self.tw.turtles.get_active_turtle().stop_fill()) + self.tw.lc.def_prim('stopfill', 0, + Primitive(Turtle.stop_fill)) palette.add_block('pensize', style='box-style', @@ -493,10 +504,8 @@ in place of a number block)'), value_block=True, prim_name='pensize', logo_command='pensize') - self.tw.lc.def_prim( - 'pensize', - 0, - lambda self: self.tw.turtles.get_active_turtle().get_pen_size()) + self.tw.lc.def_prim('pensize', 0, + Primitive(Turtle.get_pen_size, return_type=TYPE_NUMBER)) define_logo_function('tapensize', 'to tapensize\noutput first round \ pensize\nend\n') @@ -507,18 +516,10 @@ pensize\nend\n') colors=["#00FFFF", "#00A0A0"], help_string=_('Palette of pen colors')) - self._make_constant(palette, 'red', _('red'), CONSTANTS['red']) - self._make_constant(palette, 'orange', _('orange'), - CONSTANTS['orange']) - self._make_constant(palette, 'yellow', _('yellow'), - CONSTANTS['yellow']) - self._make_constant(palette, 'green', _('green'), CONSTANTS['green']) - self._make_constant(palette, 'cyan', _('cyan'), CONSTANTS['cyan']) - self._make_constant(palette, 'blue', _('blue'), CONSTANTS['blue']) - self._make_constant(palette, 'purple', _('purple'), - CONSTANTS['purple']) - self._make_constant(palette, 'white', _('white'), CONSTANTS['white']) - self._make_constant(palette, 'black', _('black'), CONSTANTS['black']) + color_names = ('red', 'orange', 'yellow', 'green', 'cyan', 'blue', + 'purple', 'white', 'black') + for name in color_names: + self._make_constant(palette, name, _(name), name) # In order to map Turtle Art colors to the standard UCB Logo palette, # we need to define a somewhat complex set of functions. @@ -600,7 +601,6 @@ tasetshade :shade \n') colors=["#FF00FF", "#A000A0"], help_string=_('Palette of numeric operators')) - primitive_dictionary['plus'] = self._prim_plus palette.add_block('plus2', style='number-style', label='+', @@ -609,10 +609,14 @@ tasetshade :shade \n') prim_name='plus', logo_command='sum', help_string=_('adds two alphanumeric inputs')) - self.tw.lc.def_prim( - 'plus', 2, lambda self, x, y: primitive_dictionary['plus'](x, y)) + self.tw.lc.def_prim('plus', 2, + # add up two numbers ... + or_(Primitive(Primitive.plus, return_type=TYPE_NUMBER, + arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)]), + # ... or concatenate two strings + Primitive(Primitive.plus, return_type=TYPE_STRING, + arg_descs=[ArgSlot(TYPE_STRING), ArgSlot(TYPE_STRING)]))) - primitive_dictionary['minus'] = self._prim_minus palette.add_block('minus2', style='number-style-porch', label=' –', @@ -621,12 +625,12 @@ tasetshade :shade \n') logo_command='taminus', help_string=_('subtracts bottom numeric input from \ top numeric input')) - self.tw.lc.def_prim( - 'minus', 2, lambda self, x, y: primitive_dictionary['minus'](x, y)) + self.tw.lc.def_prim('minus', 2, + Primitive(Primitive.minus, return_type=TYPE_NUMBER, + arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)])) define_logo_function('taminus', 'to taminus :y :x\noutput sum :x \ minus :y\nend\n') - primitive_dictionary['product'] = self._prim_product palette.add_block('product2', style='number-style', label='×', @@ -634,11 +638,10 @@ minus :y\nend\n') prim_name='product', logo_command='product', help_string=_('multiplies two numeric inputs')) - self.tw.lc.def_prim( - 'product', 2, - lambda self, x, y: primitive_dictionary['product'](x, y)) + self.tw.lc.def_prim('product', 2, + Primitive(Primitive.multiply, return_type=TYPE_NUMBER, + arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)])) - primitive_dictionary['division'] = self._prim_careful_divide palette.add_block('division2', style='number-style-porch', label=' /', @@ -647,11 +650,10 @@ minus :y\nend\n') logo_command='quotient', help_string=_('divides top numeric input \ (numerator) by bottom numeric input (denominator)')) - self.tw.lc.def_prim( - 'division', 2, - lambda self, x, y: primitive_dictionary['division'](x, y)) + self.tw.lc.def_prim('division', 2, + Primitive(Primitive.divide, return_type=TYPE_NUMBER, + arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)])) - primitive_dictionary['id'] = self._prim_identity palette.add_block('identity2', style='number-style-1arg', label='←', @@ -660,9 +662,24 @@ minus :y\nend\n') help_string=_('identity operator used for extending \ blocks')) self.tw.lc.def_prim('id', 1, - lambda self, x: primitive_dictionary['id'](x)) + # preserve the Type of the argument: try less general types first + or_(Primitive(Primitive.identity, return_type=TYPE_NUMERIC_STRING, + arg_descs=[ArgSlot(TYPE_NUMERIC_STRING)]), + Primitive(Primitive.identity, return_type=TYPE_CHAR, + arg_descs=[ArgSlot(TYPE_CHAR)]), + Primitive(Primitive.identity, return_type=TYPE_COLOR, + arg_descs=[ArgSlot(TYPE_COLOR)]), + Primitive(Primitive.identity, return_type=TYPE_FLOAT, + arg_descs=[ArgSlot(TYPE_FLOAT)]), + Primitive(Primitive.identity, return_type=TYPE_INT, + arg_descs=[ArgSlot(TYPE_INT)]), + Primitive(Primitive.identity, return_type=TYPE_NUMBER, + arg_descs=[ArgSlot(TYPE_NUMBER)]), + Primitive(Primitive.identity, return_type=TYPE_STRING, + arg_descs=[ArgSlot(TYPE_STRING)]), + Primitive(Primitive.identity, return_type=TYPE_OBJECT, + arg_descs=[ArgSlot(TYPE_OBJECT)]))) - primitive_dictionary['remainder'] = self._prim_mod palette.add_block('remainder2', style='number-style-porch', label=_('mod'), @@ -671,10 +688,9 @@ blocks')) logo_command='remainder', help_string=_('modular (remainder) operator')) self.tw.lc.def_prim('remainder', 2, - lambda self, x, y: - primitive_dictionary['remainder'](x, y)) + Primitive(Primitive.modulo, return_type=TYPE_NUMBER, + arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)])) - primitive_dictionary['sqrt'] = self._prim_sqrt palette.add_block('sqrt', style='number-style-1arg', label=_('√'), @@ -683,9 +699,9 @@ blocks')) logo_command='tasqrt', help_string=_('calculates square root')) self.tw.lc.def_prim('sqrt', 1, - lambda self, x: primitive_dictionary['sqrt'](x)) + Primitive(Primitive.square_root, return_type=TYPE_FLOAT, + arg_descs=[ArgSlot(TYPE_NUMBER)])) - primitive_dictionary['random'] = self._prim_random palette.add_block('random', style='number-style-block', label=[_('random'), _('min'), _('max')], @@ -694,9 +710,17 @@ blocks')) logo_command='tarandom', help_string=_('returns random number between \ minimum (top) and maximum (bottom) values')) - self.tw.lc.def_prim( - 'random', 2, lambda self, x, y: primitive_dictionary['random']( - x, y)) + + self.tw.lc.def_prim('random', 2, + or_(# random character ... + Primitive(Primitive.random_char, return_type=TYPE_CHAR, + arg_descs=[ + ArgSlot(TYPE_INT, wrapper=self.prim_cache["ord"]), + ArgSlot(TYPE_INT, wrapper=self.prim_cache["ord"])]), + # ... or random number + Primitive(Primitive.random_int, return_type=TYPE_INT, + arg_descs=[ArgSlot(TYPE_INT), ArgSlot(TYPE_INT)]))) + define_logo_function('tarandom', 'to tarandom :min :max\n \ output (random (:max - :min)) + :min\nend\n') @@ -708,7 +732,6 @@ output (random (:max - :min)) + :min\nend\n') help_string=_('used as numeric input in mathematic \ operators')) - primitive_dictionary['more'] = self._prim_more palette.add_block('greater2', style='compare-porch-style', label=' >', @@ -717,11 +740,13 @@ operators')) prim_name='greater?', logo_command='greater?', help_string=_('logical greater-than operator')) - self.tw.lc.def_prim( - 'greater?', 2, - lambda self, x, y: primitive_dictionary['more'](x, y)) + self.tw.lc.def_prim('greater?', 2, + Primitive(Primitive.greater, return_type=TYPE_BOOL, + arg_descs=or_([ArgSlot(TYPE_COLOR), ArgSlot(TYPE_COLOR)], + [ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)], + [ArgSlot(TYPE_STRING), ArgSlot(TYPE_STRING)], + [ArgSlot(TYPE_OBJECT), ArgSlot(TYPE_OBJECT)]))) - primitive_dictionary['less'] = self._prim_less palette.add_block('less2', style='compare-porch-style', label=' <', @@ -730,10 +755,13 @@ operators')) prim_name='less?', logo_command='less?', help_string=_('logical less-than operator')) - self.tw.lc.def_prim( - 'less?', 2, lambda self, x, y: primitive_dictionary['less'](x, y)) + self.tw.lc.def_prim('less?', 2, + Primitive(Primitive.less, return_type=TYPE_BOOL, + arg_descs=or_([ArgSlot(TYPE_COLOR), ArgSlot(TYPE_COLOR)], + [ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)], + [ArgSlot(TYPE_STRING), ArgSlot(TYPE_STRING)], + [ArgSlot(TYPE_OBJECT), ArgSlot(TYPE_OBJECT)]))) - primitive_dictionary['equal'] = self._prim_equal palette.add_block('equal2', style='compare-style', label='=', @@ -743,8 +771,11 @@ operators')) logo_command='equal?', help_string=_('logical equal-to operator')) self.tw.lc.def_prim('equal?', 2, - lambda self, x, y: - primitive_dictionary['equal'](x, y)) + Primitive(Primitive.equals, return_type=TYPE_BOOL, + arg_descs=or_([ArgSlot(TYPE_COLOR), ArgSlot(TYPE_COLOR)], + [ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)], + [ArgSlot(TYPE_STRING), ArgSlot(TYPE_STRING)], + [ArgSlot(TYPE_OBJECT), ArgSlot(TYPE_OBJECT)]))) palette.add_block('not', style='not-style', @@ -752,9 +783,10 @@ 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_, return_type=TYPE_BOOL, + arg_descs=[ArgSlot(TYPE_BOOL)])) - primitive_dictionary['and'] = self._prim_and palette.add_block('and2', style='boolean-style', label=_('and'), @@ -762,10 +794,10 @@ operators')) logo_command='and', special_name=_('and'), help_string=_('logical AND operator')) - self.tw.lc.def_prim( - 'and', 2, lambda self, x, y: primitive_dictionary['and'](x, y)) + self.tw.lc.def_prim('and', 2, + Primitive(Primitive.and_, return_type=TYPE_BOOL, + arg_descs=[ArgSlot(TYPE_BOOL), ArgSlot(TYPE_BOOL)])) - primitive_dictionary['or'] = self._prim_or palette.add_block('or2', style='boolean-style', label=_('or'), @@ -773,8 +805,9 @@ operators')) logo_command='or', special_name=_('or'), help_string=_('logical OR operator')) - self.tw.lc.def_prim( - 'or', 2, lambda self, x, y: primitive_dictionary['or'](x, y)) + self.tw.lc.def_prim('or', 2, + Primitive(Primitive.or_, return_type=TYPE_BOOL, + arg_descs=[ArgSlot(TYPE_BOOL), ArgSlot(TYPE_BOOL)])) def _flow_palette(self): ''' The basic Turtle Art flow palette ''' @@ -783,7 +816,6 @@ operators')) colors=["#FFC000", "#A08000"], help_string=_('Palette of flow operators')) - primitive_dictionary['wait'] = self._prim_wait palette.add_block('wait', style='basic-style-1arg', label=_('wait'), @@ -792,9 +824,10 @@ operators')) logo_command='wait', help_string=_('pauses program execution a specified \ number of seconds')) - self.tw.lc.def_prim('wait', 1, primitive_dictionary['wait'], True) + self.tw.lc.def_prim('wait', 1, + Primitive(self.tw.lc.prim_wait, arg_descs=[ArgSlot(TYPE_NUMBER)]), + True) - primitive_dictionary['forever'] = self._prim_forever palette.add_block('forever', style='clamp-style', label=_('forever'), @@ -802,10 +835,13 @@ 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, + arg_descs=[ + ConstantArg(Primitive(Primitive.controller_forever)), + ArgSlot(TYPE_OBJECT, call_arg=False)]), + True) - primitive_dictionary['repeat'] = self._prim_repeat palette.add_block('repeat', style='clamp-style-1arg', label=_('repeat'), @@ -814,9 +850,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, + arg_descs=[ArgSlot(TYPE_OBJECT, + wrapper=Primitive(Primitive.controller_repeat, + arg_descs=[ArgSlot(TYPE_INT)])), + ArgSlot(TYPE_OBJECT, call_arg=False)]), + True) - primitive_dictionary['if'] = self._prim_if palette.add_block('if', style='clamp-style-boolean', label=[_('if'), _('then'), ''], @@ -826,9 +867,11 @@ 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, + arg_descs=[ArgSlot(TYPE_BOOL), ArgSlot(TYPE_OBJECT)]), + True) - primitive_dictionary['ifelse'] = self._prim_ifelse palette.add_block('ifelse', hidden=True, # Too big to fit palette style='clamp-style-else', @@ -839,7 +882,11 @@ 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, + arg_descs=[ArgSlot(TYPE_BOOL), ArgSlot(TYPE_OBJECT), + ArgSlot(TYPE_OBJECT)]), + True) # macro palette.add_block('ifthenelse', @@ -856,7 +903,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', @@ -864,9 +912,9 @@ 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', style='basic-style-tail', label=_('stop action'), @@ -874,7 +922,7 @@ boolean operators from Numbers palette')) logo_command='stop', help_string=_('stops current action')) self.tw.lc.def_prim('stopstack', 0, - lambda self: primitive_dictionary['stopstack']()) + Primitive(self.tw.lc.prim_stop_stack)) def _blocks_palette(self): ''' The basic Turtle Art blocks palette ''' @@ -883,7 +931,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'), @@ -892,7 +939,11 @@ 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(Primitive.group, arg_descs=[ConstantArg([ + Primitive(self.tw.lc.prim_start, + export_me=False), + Primitive(self.tw.lc.prim_define_stack, + arg_descs=[ConstantArg('start')])])])) palette.add_block('string', style='box-style', @@ -909,9 +960,12 @@ buttons')) default=_('action'), logo_command='to action', help_string=_('top of nameable action stack')) - self.tw.lc.def_prim('nop3', 1, lambda self, x: None) + self.tw.lc.def_prim('nop3', 1, + Primitive(self.tw.lc.prim_define_stack, + arg_descs=[ArgSlot(TYPE_STRING)])) - primitive_dictionary['stack'] = self._prim_stack + primitive_dictionary['stack'] = Primitive(self.tw.lc.prim_invoke_stack, + arg_descs=[ArgSlot(TYPE_STRING)]) palette.add_block('stack', style='basic-style-1arg', label=_('action'), @@ -922,7 +976,6 @@ buttons')) help_string=_('invokes named action stack')) self.tw.lc.def_prim('stack', 1, primitive_dictionary['stack'], True) - primitive_dictionary['setbox'] = self._prim_setbox palette.add_block('storeinbox1', hidden=True, style='basic-style-1arg', @@ -933,9 +986,8 @@ buttons')) logo_command='make "box1', help_string=_('stores numeric value in Variable 1')) self.tw.lc.def_prim('storeinbox1', 1, - lambda self, x: - primitive_dictionary['setbox'] - ('box1', None, x)) + Primitive(self.tw.lc.prim_set_box, + arg_descs=[ConstantArg('box1'), ArgSlot(TYPE_OBJECT)])) palette.add_block('storeinbox2', hidden=True, @@ -947,9 +999,8 @@ buttons')) logo_command='make "box2', help_string=_('stores numeric value in Variable 2')) self.tw.lc.def_prim('storeinbox2', 1, - lambda self, x: - primitive_dictionary['setbox'] - ('box2', None, x)) + Primitive(self.tw.lc.prim_set_box, + arg_descs=[ConstantArg('box2'), ArgSlot(TYPE_OBJECT)])) palette.add_block('box1', hidden=True, @@ -959,7 +1010,9 @@ buttons')) logo_command=':box1', help_string=_('Variable 1 (numeric value)'), value_block=True) - self.tw.lc.def_prim('box1', 0, lambda self: self.tw.lc.boxes['box1']) + self.tw.lc.def_prim('box1', 0, + Primitive(self.tw.lc.prim_get_box, return_type=TYPE_BOX, + arg_descs=[ConstantArg('box1')])) palette.add_block('box2', hidden=True, @@ -969,8 +1022,12 @@ buttons')) logo_command=':box2', help_string=_('Variable 2 (numeric value)'), value_block=True) - self.tw.lc.def_prim('box2', 0, lambda self: self.tw.lc.boxes['box2']) + self.tw.lc.def_prim('box2', 0, + Primitive(self.tw.lc.prim_get_box, return_type=TYPE_BOX, + arg_descs=[ConstantArg('box2')])) + primitive_dictionary['setbox'] = Primitive(self.tw.lc.prim_set_box, + arg_descs=[ArgSlot(TYPE_OBJECT), ArgSlot(TYPE_OBJECT)]) palette.add_block('storein', style='basic-style-2arg', label=[_('store in'), _('box'), _('value')], @@ -980,12 +1037,11 @@ buttons')) default=[_('my box'), 100], help_string=_('stores numeric value in named \ variable')) - self.tw.lc.def_prim('storeinbox', 2, - lambda self, x, y: - primitive_dictionary['setbox'] - ('box3', x, y)) + self.tw.lc.def_prim('storeinbox', 2, primitive_dictionary['setbox']) - primitive_dictionary['box'] = self._prim_box + primitive_dictionary['box'] = Primitive(self.tw.lc.prim_get_box, + return_type=TYPE_BOX, + arg_descs=[ArgSlot(TYPE_OBJECT)]) palette.add_block('box', style='number-style-1strarg', hidden=True, @@ -996,8 +1052,7 @@ variable')) logo_command='box', value_block=True, help_string=_('named variable (numeric value)')) - self.tw.lc.def_prim('box', 1, - lambda self, x: primitive_dictionary['box'](x)) + self.tw.lc.def_prim('box', 1, primitive_dictionary['box']) palette.add_block('hat1', hidden=True, @@ -1006,7 +1061,9 @@ variable')) prim_name='nop1', logo_command='to stack1\n', help_string=_('top of Action 1 stack')) - self.tw.lc.def_prim('nop1', 0, lambda self: None) + self.tw.lc.def_prim('nop1', 0, + Primitive(self.tw.lc.prim_define_stack, + arg_descs=[ConstantArg('stack1')])) palette.add_block('hat2', hidden=True, @@ -1015,9 +1072,10 @@ variable')) prim_name='nop2', logo_command='to stack2\n', help_string=_('top of Action 2 stack')) - self.tw.lc.def_prim('nop2', 0, lambda self: None) + self.tw.lc.def_prim('nop2', 0, + Primitive(self.tw.lc.prim_define_stack, + arg_descs=[ConstantArg('stack2')])) - primitive_dictionary['stack1'] = self._prim_stack1 palette.add_block('stack1', hidden=True, style='basic-style-extended-vertical', @@ -1025,9 +1083,11 @@ variable')) prim_name='stack1', logo_command='stack1', help_string=_('invokes Action 1 stack')) - self.tw.lc.def_prim('stack1', 0, primitive_dictionary['stack1'], True) + self.tw.lc.def_prim('stack1', 0, + Primitive(self.tw.lc.prim_invoke_stack, + arg_descs=[ConstantArg('stack1')]), + True) - primitive_dictionary['stack2'] = self._prim_stack2 palette.add_block('stack2', hidden=True, style='basic-style-extended-vertical', @@ -1035,7 +1095,10 @@ variable')) prim_name='stack2', logo_command='stack2', help_string=_('invokes Action 2 stack')) - self.tw.lc.def_prim('stack2', 0, primitive_dictionary['stack2'], True) + self.tw.lc.def_prim('stack2', 0, + Primitive(self.tw.lc.prim_invoke_stack, + arg_descs=[ConstantArg('stack2')]), + True) def _trash_palette(self): ''' The basic Turtle Art turtle palette ''' @@ -1059,19 +1122,9 @@ variable')) label=_('clear all'), help_string=_('move all blocks to trash')) - # Block primitives - - def _prim_clear(self): - self.tw.lc.prim_clear() - self.tw.turtles.reset_turtles() - - def _prim_and(self, x, y): - ''' Logical and ''' - return x & y + # Callbacks to update labels after executing a block - 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', @@ -1084,66 +1137,9 @@ variable')) self.tw.lc.update_label_value( 'heading', self.tw.turtles.get_active_turtle().get_heading()) - - def _prim_box(self, x): - ''' Retrieve value from named box ''' - if isinstance(convert(x, float, False), float): - if int(float(x)) == x: - x = int(x) - try: - return self.tw.lc.boxes['box3' + str(x)] - except KeyError: - raise logoerror("#emptybox") - - def _prim_forever(self, blklist): - ''' Do list forever ''' - while True: - self.tw.lc.icall(self.tw.lc.evline, blklist[:]) - yield True - if self.tw.lc.procstop: - 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 - else: - self.tw.lc.ijmp(self.tw.lc.evline, list2[:]) - yield True - - def _prim_move(self, cmd, value1, value2=None, 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) + + def after_move(self, *ignored_args, **ignored_kwargs): + ''' Update labels after moving the turtle ''' if self.tw.lc.update_values: self.tw.lc.update_label_value( 'xcor', @@ -1154,323 +1150,38 @@ variable')) self.tw.turtles.get_active_turtle().get_xy()[1] / self.tw.coord_scale) - def _prim_or(self, x, y): - ''' Logical or ''' - return x | y - - def _prim_repeat(self, num, blklist): - ''' Repeat list num times. ''' - if not _num_type(num): - raise logoerror("#notanumber") - num = self.tw.lc.int(num) - for i in range(num): - self.tw.lc.icall(self.tw.lc.evline, blklist[:]) - yield True - if self.tw.lc.procstop: - break - self.tw.lc.ireturn() - yield True - - def _prim_right(self, value, reverse=False): - ''' Turtle rotates clockwise ''' - 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)) + def after_right(self, *ignored_args): if self.tw.lc.update_values: self.tw.lc.update_label_value( 'heading', self.tw.turtles.get_active_turtle().get_heading()) - def _prim_set(self, name, cmd, value=None): - ''' Set a value and update the associated value blocks ''' + def after_set(self, name, value=None): + ''' Update the associated value blocks ''' if value is not None: - cmd(value) 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: - if isinstance(convert(x, float, False), float): - if int(float(x)) == x: - x = int(x) - self.tw.lc.boxes[name + str(x)] = val - if self.tw.lc.update_values: - self.tw.lc.update_label_value('box', val, label=x) - else: - self.tw.lc.boxes[name] = val - if self.tw.lc.update_values: - self.tw.lc.update_label_value(name, val) - - def _prim_stack(self, x): - ''' Process a named stack ''' - if isinstance(convert(x, float, False), float): - if int(float(x)) == x: - x = int(x) - if 'stack3' + str(x) not in self.tw.lc.stacks or \ - self.tw.lc.stacks['stack3' + str(x)] is None: - raise logoerror("#nostack") - self.tw.lc.icall(self.tw.lc.evline, - self.tw.lc.stacks['stack3' + str(x)][:]) - yield True - self.tw.lc.procstop = False - self.tw.lc.ireturn() - yield True - - def _prim_stack1(self): - ''' Process Stack 1 ''' - if self.tw.lc.stacks['stack1'] is None: - raise logoerror("#nostack") - self.tw.lc.icall(self.tw.lc.evline, - self.tw.lc.stacks['stack1'][:]) - yield True - self.tw.lc.procstop = False - self.tw.lc.ireturn() - yield True - - def _prim_stack2(self): - ''' Process Stack 2 ''' - if self.tw.lc.stacks['stack2'] is None: - raise logoerror("#nostack") - self.tw.lc.icall(self.tw.lc.evline, self.tw.lc.stacks['stack2'][:]) - yield True - self.tw.lc.procstop = False - 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 - - def _prim_wait(self, wait_time): - ''' Show the turtle while we wait ''' - self.tw.turtles.get_active_turtle().show() - endtime = _millisecond() + wait_time * 1000. - while _millisecond() < endtime: - sleep(wait_time / 10.) - yield True - self.tw.turtles.get_active_turtle().hide() - self.tw.lc.ireturn() - yield True - - # Math primitivies - - def _prim_careful_divide(self, x, y): - ''' Raise error on divide by zero ''' - if isinstance(x, list) and _num_type(y): - z = [] - for i in range(len(x)): - try: - z.append(x[i] / y) - except ZeroDivisionError: - raise logoerror("#zerodivide") - return z - try: - return x / y - except ZeroDivisionError: - raise logoerror("#zerodivide") - except TypeError: - try: - return self._string_to_num(x) / self._string_to_num(y) - except ZeroDivisionError: - raise logoerror("#zerodivide") - except ValueError: - raise logoerror("#syntaxerror") - except TypeError: - raise logoerror("#notanumber") - - def _prim_equal(self, x, y): - ''' Numeric and logical equal ''' - if isinstance(x, list) and isinstance(y, list): - for i in range(len(x)): - if x[i] != y[i]: - return False - return True - try: - return float(x) == float(y) - except ValueError: - typex, typey = False, False - if strtype(x): - typex = True - if strtype(y): - typey = True - if typex and typey: - return x == y - try: - return self._string_to_num(x) == self._string_to_num(y) - except TypeError: - raise logoerror("#syntaxerror") - - def _prim_less(self, x, y): - ''' Compare numbers and strings ''' - if isinstance(x, list) or isinstance(y, list): - raise logoerror("#syntaxerror") - try: - return float(x) < float(y) - except ValueError: - typex, typey = False, False - if strtype(x): - typex = True - if strtype(y): - typey = True - if typex and typey: - return x < y - try: - return self._string_to_num(x) < self._string_to_num(y) - except TypeError: - raise logoerror("#notanumber") - - def _prim_more(self, x, y): - ''' Compare numbers and strings ''' - return self._prim_less(y, x) - - def _prim_plus(self, x, y): - ''' Add numbers, concat strings ''' - if x in COLORDICT: - x = _color_to_num(x) - if y in COLORDICT: - y = _color_to_num(y) - if _num_type(x) and _num_type(y): - return(x + y) - elif isinstance(x, list) and isinstance(y, list): - z = [] - for i in range(len(x)): - z.append(x[i] + y[i]) - return(z) - else: - if _num_type(x): - xx = str(round_int(x)) - else: - xx = str(x) - if _num_type(y): - yy = str(round_int(y)) - else: - yy = str(y) - return(xx + yy) - - def _prim_minus(self, x, y): - ''' Numerical subtraction ''' - if _num_type(x) and _num_type(y): - return(x - y) - elif isinstance(x, list) and isinstance(y, list): - z = [] - for i in range(len(x)): - z.append(x[i] - y[i]) - return(z) - try: - return self._string_to_num(x) - self._string_to_num(y) - except TypeError: - raise logoerror("#notanumber") - - def _prim_product(self, x, y): - ''' Numerical multiplication ''' - if _num_type(x) and _num_type(y): - return(x * y) - elif isinstance(x, list) and _num_type(y): - z = [] - for i in range(len(x)): - z.append(x[i] * y) - return(z) - elif isinstance(y, list) and _num_type(x): - z = [] - for i in range(len(y)): - z.append(y[i] * x) - return(z) - try: - return self._string_to_num(x) * self._string_to_num(y) - except TypeError: - raise logoerror("#notanumber") - - def _prim_mod(self, x, y): - ''' Numerical mod ''' - if _num_type(x) and _num_type(y): - return(x % y) - try: - return self._string_to_num(x) % self._string_to_num(y) - except TypeError: - raise logoerror("#notanumber") - except ValueError: - raise logoerror("#syntaxerror") - - def _prim_sqrt(self, x): - ''' Square root ''' - if _num_type(x): - if x < 0: - raise logoerror("#negroot") - return sqrt(x) - try: - return sqrt(self._string_to_num(x)) - except ValueError: - raise logoerror("#negroot") - except TypeError: - raise logoerror("#notanumber") - - def _prim_random(self, x, y): - ''' Random integer ''' - if _num_type(x) and _num_type(y): - return(int(round(uniform(x, y), 0))) - xx, xflag = chr_to_ord(x) - yy, yflag = chr_to_ord(y) - if xflag and yflag: - return chr(int(round(uniform(xx, yy), 0))) - if not xflag: - xx = self._string_to_num(x) - if not yflag: - yy = self._string_to_num(y) - try: - return(int(round(uniform(xx, yy), 0))) - except TypeError: - raise logoerror("#notanumber") - - def _prim_identity(self, x): - ''' Identity function ''' - return(x) - # Utilities - def _string_to_num(self, x): - ''' Try to comvert a string to a number ''' - if isinstance(x, (int, float)): - return(x) - try: - return int(ord(x)) - except TypeError: - pass - if isinstance(x, list): - raise logoerror("#syntaxerror") - if x in COLORDICT: - return _color_to_num(x) - xx = convert(x.replace(self.tw.decimal_point, '.'), float) - if isinstance(xx, float): - return xx - else: - xx, xflag = chr_to_ord(x) - if xflag: - return xx - else: - raise logoerror("#syntaxerror") - - def _make_constant(self, palette, block_name, label, constant): + def _make_constant(self, palette, block_name, label, constant_key): ''' Factory for constant blocks ''' - if constant in COLORDICT: - if COLORDICT[constant][0] is not None: - value = str(COLORDICT[constant][0]) + constant = CONSTANTS[constant_key] + if isinstance(constant, Color): + if constant.color is not None: + logo_command = str(constant.color) else: # Black or White - value = '0 tasetshade %d' % (COLORDICT[constant][1]) + logo_command = '0 tasetshade %d' % (constant.shade) + return_type = TYPE_COLOR else: - value = constant + logo_command = constant + return_type = TYPE_NUMBER palette.add_block(block_name, style='box-style', label=label, prim_name=block_name, - logo_command=value) - self.tw.lc.def_prim(block_name, 0, lambda self: constant) + logo_command=logo_command) + self.tw.lc.def_prim(block_name, 0, + Primitive(CONSTANTS.get, return_type=return_type, + arg_descs=[ConstantArg(constant_key)])) diff --git a/TurtleArt/tablock.py b/TurtleArt/tablock.py index ff392e0..b39ceaa 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, COLORDICT) + GRADIENT_COLOR, EXPANDABLE_FLOW, Color, + MEDIA_BLOCK2TYPE) from tapalette import (palette_blocks, block_colors, expandable_blocks, content_blocks, block_names, block_primitives, block_styles, special_block_colors) @@ -34,6 +35,36 @@ import sprites from tautils import (debug_output, error_output) +media_blocks_dictionary = {} # new media blocks get added here + +class Media(object): + """ Media objects can be images, audio files, videos, Journal + descriptions, or camera snapshots. """ + + ALL_TYPES = ('media', 'audio', 'video', 'descr', 'camera', 'camera1') + + def __init__(self, type_, value=None): + """ + type_ --- a string that indicates the kind of media: + media --- image + audio --- audio file + video --- video + descr --- Journal description + camera, camera1 --- camera snapshot + value --- a file path or a reference to a Sugar datastore object """ + if type_ not in Media.ALL_TYPES: + raise ValueError("Media.type must be one of " + + repr(Media.ALL_TYPES)) + self.type = type_ + self.value = value + + def __str__(self): + return '%s_%s' % (self.type, str(self.value)) + + def __repr__(self): + return 'Media(type=%s, value=%s)' % (repr(self.type), repr(self.value)) + + class Blocks: """ A class for the list of blocks and everything they share in common """ @@ -241,6 +272,13 @@ class Block: self.block_list.append_to_list(self) + def __repr__(self): + if self.is_value_block(): + name = self.get_value() + else: + name = self.name + return 'Block(%s)' % (repr(name)) + def get_visibility(self): ''' Should block be visible on the palette? ''' return self._visible @@ -277,6 +315,44 @@ 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, add_type_prefix=True): + """ Return the value stored in this value block or None if this is + not a value block + add_type_prefix -- prepend a prefix to indicate the type of the + 'raw' value """ + if not self.is_value_block(): + return None + + 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 add_type_prefix: + result = '#s' + else: + result = '' + if isinstance(self.values[0], (float, int)): + if int(self.values[0]) == self.values[0]: + self.values[0] = int(self.values[0]) + result += str(self.values[0]) + else: + result += self.values[0] + return result + elif self.name in MEDIA_BLOCK2TYPE: + return Media(MEDIA_BLOCK2TYPE[self.name], self.values[0]) + elif self.name in media_blocks_dictionary: + return Media('media', self.name.upper()) + else: + return None + def highlight(self): """ We may want to highlight a block... """ if self.spr is not None and self.status is not 'collapsed': @@ -529,10 +605,8 @@ class Block: else: self._set_labels(i, str(v)) elif self.type == 'block' and self.name in CONSTANTS: - if CONSTANTS[self.name] in COLORDICT: - v = COLORDICT[CONSTANTS[self.name]][0] - if v is None: - v = COLORDICT[CONSTANTS[self.name]][1] + if isinstance(CONSTANTS[self.name], Color): + v = int(CONSTANTS[self.name]) else: v = CONSTANTS[self.name] self._set_labels(0, block_names[self.name][0] + ' = ' + str(v)) @@ -973,7 +1047,7 @@ class Block: self._make_block_graphics(svg, self.svg.basic_block) self.docks = [['flow', True, self.svg.docks[0][0], self.svg.docks[0][1]], - ['unavailable', True, 0, self.svg.docks[0][1] + 10, '['], + ['flow', True, 0, self.svg.docks[0][1] + 10, '['], ['flow', False, self.svg.docks[1][0], self.svg.docks[1][1], ']']] diff --git a/TurtleArt/tacanvas.py b/TurtleArt/tacanvas.py index 89b8ed1..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/> @@ -28,7 +28,7 @@ import cairo import pangocairo from tautils import get_path -from taconstants import COLORDICT, TMP_SVG_PATH +from taconstants import Color, TMP_SVG_PATH def wrap100(n): @@ -208,19 +208,19 @@ class TurtleGraphics: save_rgb = self._fgrgb[:] # Special case for color blocks - if color in COLORDICT: - if COLORDICT[color][0] is None: - self._shade = COLORDICT[color][1] + if isinstance(color, Color): + if color.color is None: + self._shade = color.shade else: - self._color = COLORDICT[color][0] + self._color = color.color else: self._color = color - if shade in COLORDICT: - self._shade = COLORDICT[shade][1] + if isinstance(shade, Color): + self._shade = shade.shade else: self._shade = shade - if gray in COLORDICT: - self._gray = COLORDICT[gray][2] + if isinstance(gray, Color): + self._gray = gray.gray else: self._gray = gray diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py index 835209e..9666573 100644 --- a/TurtleArt/taconstants.py +++ b/TurtleArt/taconstants.py @@ -79,20 +79,104 @@ XO4 = 'xo4' UNKNOWN = 'unknown' TMP_SVG_PATH = '/tmp/turtle_output.svg' + + +class Color(object): + """ A color used in block programs (e.g., as pen color). """ + + def __init__(self, name, color=0, shade=50, gray=100): + """ name -- a string with the name of the color, e.g., 'red' + color -- the hue (0-100, or None for white, gray, and black) + shade -- the lightness (0 is black, 100 is white) + gray -- the saturation (0 is gray, 100 is fully saturated) """ + self.name = name + self.color = color + self.shade = shade + self.gray = gray + + def __int__(self): + if self.color is None: + return int(self.shade) + else: + return int(self.color) + + def __float__(self): + return float(int(self)) + + def get_number_string(self): + return str(int(self)) + + def __str__(self): + return str(self.name) + + def __repr__(self): + return '%s (%s/%d/%d)' % (str(self.name), str(self.color), + self.shade, self.gray) + + def __eq__(self, other): + """ A Color is equivalent to + * another Color with the same color, shade, and gray values + * an integer, float, or long that equals int(self) """ + if isinstance(other, Color): + return (self.color == other.color and self.shade == other.shade + and self.gray == other.gray) + elif isinstance(other, (int, float, long)): + return int(self) == other + ## * a basestring that equals str(self) + #elif isinstance(other, basestring): + # return str(self) == other + else: + return False + + def __lt__(self, other): + """ A Color is less than + * another Color whose name appears earlier in the alphabet + * a number that is less than int(self) + * a string that appears before the underscore in the ASCII table """ + if isinstance(other, Color): + return str(self) < str(other) + elif isinstance(other, (int, float, long)): + return int(self) < other + elif isinstance(other, basestring): + return '_' + str(self) < other + else: + return False + + def __gt__(self, other): + """ A Color is greater than + * another Color whose name appears later in the alphabet + * a number that is greater than int(self) + * a string that appears after the underscore in the ASCII table """ + if isinstance(other, Color): + return str(self) > str(other) + elif isinstance(other, (int, float, long)): + return int(self) > other + elif isinstance(other, basestring): + return '_' + str(self) > other + else: + return False + + def is_gray(self): + """ Return True iff this color is white, gray, or black, i.e. if its + hue is not set or its saturation is zero. """ + return self.color is None or not self.gray + + + CONSTANTS = {'leftpos': None, 'toppos': None, 'rightpos': None, 'bottompos': None, 'width': None, 'height': None, - 'black': '_black', 'white': '_white', 'red': '_red', - 'orange': '_orange', 'yellow': '_yellow', 'green': '_green', - 'cyan': '_cyan', 'blue': '_blue', 'purple': '_purple', + 'black': Color('black', None, 0, 0), + 'white': Color('white', None, 100, 0), + 'red': Color('red', 0, 50, 100), + 'orange': Color('orange', 10, 50, 100), + 'yellow': Color('yellow', 20, 50, 100), + 'green': Color('green', 40, 50, 100), + 'cyan': Color('cyan', 50, 50, 100), + 'blue': Color('blue', 70, 50, 100), + 'purple': Color('purple', 90, 50, 100), 'titlex': None, 'titley': None, 'leftx': None, 'topy': None, 'rightx': None, 'bottomy': None} -COLORDICT = {'_black': [None, 0, 0], '_white': [None, 100, 0], - '_red': [0, 50, 100], '_orange': [10, 50, 100], - '_yellow': [20, 50, 100], '_green': [40, 50, 100], - '_cyan': [50, 50, 100], '_blue': [70, 50, 100], - '_purple': [90, 50, 100]} - # Blocks that are expandable EXPANDABLE_STYLE = ['boolean-style', 'compare-porch-style', 'compare-style', 'number-style-porch', 'number-style', 'basic-style-2arg', @@ -113,10 +197,10 @@ OLD_DOCK = ['and', 'or', 'plus', 'minus', 'division', 'product', 'remainder'] CONTENT_ARGS = ['show', 'showaligned', 'push', 'storein', 'storeinbox1', 'storeinbox2'] -PREFIX_DICTIONARY = {} +MEDIA_BLOCK2TYPE = {} # map media blocks to media types + -# These blocks get a special skin -BLOCKS_WITH_SKIN = [] +BLOCKS_WITH_SKIN = [] # These blocks get a special skin PYTHON_SKIN = [] diff --git a/TurtleArt/taexportlogo.py b/TurtleArt/taexportlogo.py index f021f94..4a1ed4f 100644 --- a/TurtleArt/taexportlogo.py +++ b/TurtleArt/taexportlogo.py @@ -223,31 +223,31 @@ def _bottomy(tw): def _red(tw): - return CONSTANTS['red'] + return '_' + str(CONSTANTS['red']) def _orange(tw): - return CONSTANTS['orange'] + return '_' + str(CONSTANTS['orange']) def _yellow(tw): - return CONSTANTS['yellow'] + return '_' + str(CONSTANTS['yellow']) def _green(tw): - return CONSTANTS['green'] + return '_' + str(CONSTANTS['green']) def _cyan(tw): - return CONSTANTS['cyan'] + return '_' + str(CONSTANTS['cyan']) def _blue(tw): - return CONSTANTS['blue'] + return '_' + str(CONSTANTS['blue']) def _purple(tw): - return CONSTANTS['purple'] + return '_' + str(CONSTANTS['purple']) def _white(tw): diff --git a/TurtleArt/taexportpython.py b/TurtleArt/taexportpython.py new file mode 100644 index 0000000..76087ad --- /dev/null +++ b/TurtleArt/taexportpython.py @@ -0,0 +1,259 @@ +#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 (ast_yield_true, Primitive, PyExportError, + value_to_ast) +from tautils import (debug_output, find_group, find_top_block, get_stack_name) + + + +_SETUP_CODE_START = """\ +#!/usr/bin/env python + +from time import * +from random import uniform +from math import * + +from pyexported.window_setup import * + + +tw = get_tw() + +BOX = {} +ACTION = {} + + +""" +_SETUP_CODE_END = """\ + +if __name__ == '__main__': + tw.lc.start_time = time() + tw.lc.icall(start) + gobject.idle_add(tw.lc.doevalstep) + gtk.main() +""" +_ACTION_STACK_START = """\ +def %s(): +""" +_START_STACK_START_ADD = """\ + tw.start_plugins() +""" +_ACTION_STACK_PREAMBLE = """\ + 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: + stack_name = get_stack_name(block) + if stack_name: + pythoncode = _action_stack_to_python(block, tw.lc, name=stack_name) + 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") """ + if isinstance(name, int): + name = float(name) + if not isinstance(name, basestring): + name = str(name) + + # traverse the block stack and get the AST for every block + ast_list = _walk_action_stack(block, lc) + if not isinstance(ast_list[-1], ast.Yield): + 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) + if name == 'start': + pre_preamble = _START_STACK_START_ADD + else: + pre_preamble = '' + generated_code = _indent(generated_code, 1) + if generated_code.endswith(linesep): + newline = "" + else: + newline = linesep + snippets = [_ACTION_STACK_START % (name_id), + pre_preamble, + _ACTION_STACK_PREAMBLE, + generated_code, + newline, + _ACTION_STACK_END % (name, name_id)] + return "".join(snippets) + +def _walk_action_stack(top_block, lc, convert_me=True): + """ Turn a stack of blocks into a list of ASTs + convert_me -- convert values and Primitives to ASTs or return them + unconverted? """ + block = top_block + + # value blocks don't have a primitive + # (but constant blocks (colors, screen dimensions, etc.) do) + if block.is_value_block(): + raw_value = block.get_value(add_type_prefix=False) + if convert_me: + value_ast = value_to_ast(raw_value) + if value_ast is not None: + return [value_ast] + else: + return [] + else: + if raw_value is not None: + return [raw_value] + 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 convert_me: + 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) + elif new_ast is not None: + ast_list.append(new_ast) + elif arg_asts: # TODO do we ever get here? + new_ast = ast.List(elts=arg_asts, ctx=ast.Load) + ast_list.append(new_ast) + else: + ast_list.append((prim, ) + tuple(arg_asts)) + + # 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 + if dock[0] == 'flow': + # body of conditional or loop + new_arg_asts = _walk_action_stack(conn, lc, + convert_me=convert_me) + if (prim == LogoCode.prim_loop and + not isinstance(new_arg_asts[-1], ast.Yield)): + new_arg_asts.append(ast_yield_true()) + arg_asts.append(new_arg_asts) + else: + # argument block + new_arg_asts = _walk_action_stack(conn, lc, convert_me=False) + 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) + + diff --git a/TurtleArt/tajail.py b/TurtleArt/tajail.py index 40517cd..539c126 100644 --- a/TurtleArt/tajail.py +++ b/TurtleArt/tajail.py @@ -27,21 +27,11 @@ from math import * def myfunc(f, args): ''' Run inline Python code ''' # check to make sure no import calls are made - if len(args) == 1: - myf = 'def f(x): return ' + f.replace('import', '') - userdefined = {} - exec myf in globals(), userdefined - return userdefined.values()[0](args[0]) - elif len(args) == 2: - myf = 'def f(x, y): return ' + f.replace('import', '') - userdefined = {} - exec myf in globals(), userdefined - return userdefined.values()[0](args[0], args[1]) - elif len(args) == 3: - myf = 'def f(x, y, z): return ' + f.replace('import', '') - userdefined = {} - exec myf in globals(), userdefined - return userdefined.values()[0](args[0], args[1], args[2]) + params = ", ".join(['x', 'y', 'z'][:len(args)]) + myf = ''.join(['def f(', params, '): return ', f.replace('import', '')]) + userdefined = {} + exec myf in globals(), userdefined + return userdefined.values()[0](*args) def myfunc_import(parent, f, x): diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py index 0b178c6..5212acb 100644 --- a/TurtleArt/talogo.py +++ b/TurtleArt/talogo.py @@ -25,6 +25,7 @@ import gtk from time import time, sleep from operator import isNumberType +from os.path import exists as os_path_exists from UserDict import UserDict try: @@ -33,10 +34,16 @@ try: except ImportError: GRID_CELL_SIZE = 55 -from taconstants import (TAB_LAYER, DEFAULT_SCALE, PREFIX_DICTIONARY) +import traceback + +from tablock import (Block, Media, media_blocks_dictionary) +from taconstants import (TAB_LAYER, DEFAULT_SCALE) +from tajail import myfunc 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) +from tatype import (TATypeError, TYPES_NUMERIC) +from tautils import (get_pixbuf_from_journal, data_from_file, + text_media_type, round_int, debug_output, find_group, + get_stack_name) try: from util.RtfParser import RtfTextOnly @@ -46,7 +53,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 +85,21 @@ class logoerror(Exception): return str(self.value) -class HiddenBlock: +class NegativeRootError(BaseException): + """ Similar to the ZeroDivisionError, this error is raised at runtime + when trying to computer the square root of a negative number. """ + + DEFAULT_MESSAGE = 'square root of negative number' + + def __init__(self, neg_value=None, message=DEFAULT_MESSAGE): + self.neg_value = neg_value + self.message = message + + def __str__(self): + return str(self.message) + + +class HiddenBlock(Block): def __init__(self, name, value=None): self.name = name @@ -92,6 +112,7 @@ class HiddenBlock: self.connections = [] self.docks = [] + # Utility functions @@ -187,6 +208,14 @@ class LogoCode: self.oblist[string] = sym return sym + def get_prim_callable(self, name): + """ Return the callable primitive associated with the given name """ + sym = self.oblist.get(name) + if sym is not None: + return sym.fcn + else: + return None + def run_blocks(self, code): """Run code generated by generate_code(). """ @@ -219,42 +248,18 @@ class LogoCode: for b in blocks: b.unhighlight() - # Hidden macro expansions - for b in blocks: - if b.name in ['while', 'until']: - action_blk, new_blocks = self._expand_forever(b, blk, blocks) - blocks = new_blocks[:] - if b == blk: - blk = action_blk - for b in blocks: - if b.name in ['forever']: - action_blk, new_blocks = self._expand_forever(b, blk, blocks) - blocks = new_blocks[:] - if b == blk: - blk = action_blk - for b in blocks: - if b.name == 'hat1': - code = self._blocks_to_code(b) - self.stacks['stack1'] = self._readline(code) - elif b.name == 'hat2': - code = self._blocks_to_code(b) - self.stacks['stack2'] = self._readline(code) - elif b.name == 'hat': - if b.connections is not None and len(b.connections) > 1 and \ - b.connections[1] is not None: + if b.name in ('hat', 'hat1', 'hat2'): + stack_name = get_stack_name(b) + if stack_name: + stack_key = self._get_stack_key(stack_name) code = self._blocks_to_code(b) - try: - x = b.connections[1].values[0] - except IndexError: - self.tw.showlabel('#nostack') - self.tw.showblocks() - self.tw.running_blocks = False - return None - if isinstance(convert(x, float, False), float): - if int(float(x)) == x: - x = int(x) - self.stacks['stack3' + str(x)] = self._readline(code) + self.stacks[stack_key] = self._readline(code) + else: + self.tw.showlabel('#nostack') + self.tw.showblocks() + self.tw.running_blocks = False + return None code = self._blocks_to_code(blk) @@ -278,7 +283,7 @@ class LogoCode: return ['%nothing%', '%nothing%'] code = [] dock = blk.docks[0] - if len(dock) > 4: # There could be a '(', ')', '[' or ']'. + if len(dock) > 4 and dock[4] in ('[', ']', ']['): # There could be a '(', ')', '[' or ']'. code.append(dock[4]) if blk.primitive is not None: # make a tuple (prim, blk) if blk in self.tw.block_list.list: @@ -286,37 +291,19 @@ 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 is None: return ['%nothing%'] + else: + code.append(value) else: return ['%nothing%'] if blk.connections is not None and len(blk.connections) > 0: for i in range(1, len(blk.connections)): b = blk.connections[i] dock = blk.docks[i] - if len(dock) > 4: # There could be a '(', ')', '[' or ']'. + if len(dock) > 4 and dock[4] in ('[', ']', ']['): # There could be a '(', ')', '[' or ']'. for c in dock[4]: code.append(c) if b is not None: @@ -346,7 +333,9 @@ class LogoCode: bindex = None if isinstance(token, tuple): (token, bindex) = token - if isNumberType(token): + if isinstance(token, Media): + res.append(token) + elif isNumberType(token): res.append(token) elif token.isdigit(): res.append(float(token)) @@ -401,7 +390,7 @@ class LogoCode: self.istack.append(self.step) self.step = fcn(*(args)) - def evline(self, blklist): + def evline(self, blklist, call_me=True): """ Evaluate a line of code from the list. """ oldiline = self.iline self.iline = blklist[:] @@ -432,7 +421,7 @@ class LogoCode: (token, self.bindex) = self.iline[1] # Process the token and any arguments. - self.icall(self._eval) + self.icall(self._eval, call_me) yield True # Time to unhighlight the current block. @@ -455,7 +444,7 @@ class LogoCode: self.tw.display_coordinates() yield True - def _eval(self): + def _eval(self, call_me=True): """ Evaluate the next token on the line of code we are processing. """ token = self.iline.pop(0) bindex = None @@ -467,7 +456,7 @@ class LogoCode: # We highlight blocks here in case an error occurs... if not self.tw.hide and bindex is not None: self.tw.block_list.list[bindex].highlight() - self.icall(self._evalsym, token) + self.icall(self._evalsym, token, call_me) yield True # and unhighlight if everything was OK. if not self.tw.hide and bindex is not None: @@ -479,7 +468,7 @@ class LogoCode: self.ireturn(res) yield True - def _evalsym(self, token): + def _evalsym(self, token, call_me): """ Process primitive associated with symbol token """ self._undefined_check(token) oldcfun, oldarglist = self.cfun, self.arglist @@ -489,35 +478,52 @@ class LogoCode: self.tw.showblocks() self.tw.display_coordinates() raise logoerror("#noinput") + is_Primitive = type(self.cfun.fcn).__name__ == 'Primitive' + is_PrimitiveDisjunction = type(self.cfun.fcn).__name__ == 'PrimitiveDisjunction' + call_args = not (is_Primitive or is_PrimitiveDisjunction) for i in range(token.nargs): self._no_args_check() - self.icall(self._eval) + self.icall(self._eval, call_args) yield True self.arglist.append(self.iresult) + need_to_pop_istack = False if self.cfun.rprim: if isinstance(self.cfun.fcn, list): # debug_output('evalsym rprim list: %s' % (str(token)), # self.tw.running_sugar) - self.icall(self._ufuncall, self.cfun.fcn) + self.icall(self._ufuncall, self.cfun.fcn, call_args) yield True + need_to_pop_istack = True + result = None else: - self.icall(self.cfun.fcn, *self.arglist) - yield True - result = None + if call_me: + self.icall(self.cfun.fcn, *self.arglist) + yield True + need_to_pop_istack = True + result = None + else: + result = (self.cfun.fcn, ) + tuple(self.arglist) else: - result = self.cfun.fcn(self, *self.arglist) + need_to_pop_istack = True + if call_me: + result = self.cfun.fcn(self, *self.arglist) + else: + result = (self.cfun.fcn, self) + tuple(self.arglist) self.cfun, self.arglist = oldcfun, oldarglist if self.arglist is not None and result is None: self.tw.showblocks() raise logoerror("%s %s %s" % (oldcfun.name, _("did not output to"), self.cfun.name)) - self.ireturn(result) - yield True + if need_to_pop_istack: + self.ireturn(result) + yield True + else: + self.iresult = result - def _ufuncall(self, body): + def _ufuncall(self, body, call_me): """ ufuncall """ - self.ijmp(self.evline, body) + self.ijmp(self.evline, body, call_me) yield True def doevalstep(self): @@ -526,31 +532,68 @@ class LogoCode: try: while (_millisecond() - starttime) < 120: try: - if self.step is not None: + if self.step is None: + return False + if self.tw.running_turtleart: try: self.step.next() - except ValueError: + except ValueError, ve: debug_output('generator already executing', self.tw.running_sugar) self.tw.running_blocks = False return False + except TATypeError as tte: + # TODO insert the correct block name + # (self.cfun.name is only the name of the + # outermost block in this statement/ line of code) + # use logoerror("#notanumber") when possible + if (tte.req_type in TYPES_NUMERIC and + tte.bad_type not in TYPES_NUMERIC): + raise logoerror("#notanumber") + else: + raise logoerror("%s %s %s %s" + % (self.cfun.name, _("doesn't like"), + str(tte.bad_value), _("as input"))) + except ZeroDivisionError: + raise logoerror("#zerodivide") + except NegativeRootError: + raise logoerror("#negroot") + except IndexError: + raise logoerror("#emptyheap") else: - return False + try: + self.step.next() + except BaseException as error: + if isinstance(error, (StopIteration, + logoerror)): + raise error + else: + traceback.print_exc() + self.tw.showlabel('status', '%s: %s' + % (type(error).__name__, str(error))) + 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 @@ -597,19 +640,198 @@ 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.stop_playing_media() + self.reset_scale() + self.reset_timer() + self.clear_value_blocks() + self.reset_internals() + self.tw.canvas.clearscreen() + self.tw.turtles.reset_turtles() + + def stop_playing_media(self): if self.tw.gst_available: from tagplay import stop_media stop_media(self) - self.tw.canvas.clearscreen() - self.tw.turtles.reset_turtles() + + def reset_scale(self): self.scale = DEFAULT_SCALE - self.hidden_turtle = None + + def reset_timer(self): self.start_time = time() - self.clear_value_blocks() - self.tw.activity.restore_state() + + def get_start_time(self): + return self.start_time + + def reset_internals(self): + self.hidden_turtle = None + 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_clamp(self, blklist): + """ Run clamp blklist """ + self.icall(self.evline, blklist[:]) + yield True + self.procstop = False + self.ireturn() + yield True + + def prim_stop_stack(self): + """ Stop execution of a stack """ + self.procstop = True + + def prim_wait(self, wait_time): + """ Show the turtle while we wait """ + self.tw.turtles.get_active_turtle().show() + endtime = _millisecond() + wait_time * 1000. + while _millisecond() < endtime: + sleep(wait_time / 10.) + yield True + self.tw.turtles.get_active_turtle().hide() + 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 prim_set_box(self, name, value): + """ Store value in named box """ + (key, is_native) = self._get_box_key(name) + self.boxes[key] = value + if is_native: + if self.update_values: + self.update_label_value(name, value) + else: + if self.update_values: + self.update_label_value('box', value, label=name) + + def prim_get_box(self, name): + """ Retrieve value from named box """ + (key, is_native) = self._get_box_key(name) + try: + return self.boxes[key] + except KeyError: + raise logoerror("#emptybox") # FIXME this looks like a syntax error in the GUI + + def _get_box_key(self, name): + """ Return the key used for this box in the boxes dictionary and a + boolean indicating whether it is a 'native' box """ + if name in ('box1', 'box2'): + return (name, True) + else: + # make sure '5' and '5.0' point to the same box + if isinstance(name, (basestring, int, long)): + try: + name = float(name) + except ValueError: + pass + return ('box3_' + str(name), False) + + def prim_define_stack(self, name): + """ Top of a named stack """ + pass + + def prim_invoke_stack(self, name): + """ Process a named stack """ + key = self._get_stack_key(name) + if self.stacks.get(key) is None: + raise logoerror("#nostack") + self.icall(self.evline, self.stacks[key][:]) + yield True + self.procstop = False + self.ireturn() + yield True + + def _get_stack_key(self, name): + """ Return the key used for this stack in the stacks dictionary """ + if name in ('stack1', 'stack2'): + return name + else: + # make sure '5' and '5.0' point to the same action stack + if isinstance(name, (int, long)): + name = float(name) + return 'stack3' + str(name) + + def get_heap(self): + return self.heap + + def reset_heap(self): + """ Reset heap to an empty list """ + # empty the list rather than setting it to a new empty list object, + # so the object references are preserved + while self.heap: + self.heap.pop() + + def prim_myfunction(self, f, *args): + """ Programmable block (Call tajail.myfunc and convert any errors to + logoerrors) """ + try: + y = myfunc(f, args) + if str(y) == 'nan': + debug_output('Python function returned NAN', + self.tw.running_sugar) + self.stop_logo() + raise logoerror("#notanumber") + else: + return y + except ZeroDivisionError: + self.stop_logo() + raise logoerror("#zerodivide") + except ValueError, e: + self.stop_logo() + raise logoerror('#' + str(e)) + except SyntaxError, e: + self.stop_logo() + raise logoerror('#' + str(e)) + except NameError, e: + self.stop_logo() + raise logoerror('#' + str(e)) + except OverflowError: + self.stop_logo() + raise logoerror("#overflowerror") + except TypeError: + self.stop_logo() + raise logoerror("#notanumber") def clear_value_blocks(self): if not hasattr(self, 'value_blocks_to_update'): @@ -686,6 +908,75 @@ class LogoCode: for blk in drag_group: blk.spr.move_relative((dx, 0)) + def show(self, obj, center=False): + """ Show is the general-purpose media-rendering block. """ + # media + if isinstance(obj, Media) and obj.value: + self.filepath = None + self.pixbuf = None # Camera writes directly to pixbuf + self.dsobject = None + + # camera snapshot + if obj.value.lower() in media_blocks_dictionary: + media_blocks_dictionary[obj.value.lower()]() + # file path + elif os_path_exists(obj.value): + self.filepath = obj.value + # datastore object + elif self.tw.running_sugar: + from sugar.datastore import datastore + try: + self.dsobject = datastore.get(obj.value) + except: + debug_output("Couldn't find dsobject %s" % + (obj.value), self.tw.running_sugar) + if self.dsobject is not None: + self.filepath = self.dsobject.file_path + + if self.pixbuf is not None: + self.insert_image(center=center, pixbuf=True) + elif self.filepath is None: + if self.dsobject is not None: + self.tw.showlabel( + 'nojournal', + self.dsobject.metadata['title']) + else: + self.tw.showlabel('nojournal', obj.value) + debug_output("Couldn't open %s" % (obj.value), + self.tw.running_sugar) + elif obj.type == 'media': + self.insert_image(center=center) + elif obj.type == 'descr': + mimetype = None + if self.dsobject is not None and \ + 'mime_type' in self.dsobject.metadata: + mimetype = self.dsobject.metadata['mime_type'] + description = None + if self.dsobject is not None and \ + 'description' in self.dsobject.metadata: + description = self.dsobject.metadata[ + 'description'] + self.insert_desc(mimetype, description) + elif obj.type == 'audio': + self.play_sound() + elif obj.type == 'video': + self.play_video() + + if self.dsobject is not None: + self.dsobject.destroy() + + # text or number + elif isinstance(obj, (basestring, float, int)): + if isinstance(obj, (float, int)): + obj = round_int(obj) + x, y = self.x2tx(), self.y2ty() + if center: + y -= self.tw.canvas.textsize + self.tw.turtles.get_active_turtle().draw_text(obj, x, y, + int(self.tw.canvas.textsize * + self.scale / 100.), + self.tw.canvas.width - x) + def push_file_data_to_heap(self, dsobject): """ push contents of a data store object (assuming json encoding) """ data = data_from_file(dsobject.file_path) @@ -980,14 +1271,14 @@ class LogoCode: # Create a separate stacks for the forever loop and the whileflow code = self._blocks_to_code(forever_blk) - self.stacks['stack3' + str(action_name)] = self._readline(code) + self.stacks[self._get_stack_key(action_name)] = self._readline(code) if until_blk and whileflow is not None: # Create a stack from the whileflow to be called from # action_first, but then reconnect it to the ifelse block c = whileflow.connections[0] whileflow.connections[0] = None code = self._blocks_to_code(whileflow) - self.stacks['stack3' + str(action_flow_name)] = \ + self.stacks[self._get_stack_key(action_flow_name)] = \ self._readline(code) whileflow.connections[0] = c diff --git a/TurtleArt/taprimitive.py b/TurtleArt/taprimitive.py new file mode 100644 index 0000000..2eb4a90 --- /dev/null +++ b/TurtleArt/taprimitive.py @@ -0,0 +1,1105 @@ +#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 math import sqrt +from random import uniform + +#from ast_pprint import * # only used for debugging, safe to comment out + +from tablock import Media +from tacanvas import TurtleGraphics +from taconstants import (Color, CONSTANTS) +from talogo import (LogoCode, logoerror, NegativeRootError) +from taturtle import (Turtle, Turtles) +from tatype import * +from tautils import debug_output +from tawindow import (global_objects, TurtleArtWindow) +from util import ast_extensions + + +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. + """ + + _DEBUG = False + + STANDARD_OPERATORS = {'plus': (ast.UAdd, ast.Add), + 'minus': (ast.USub, ast.Sub), + 'multiply': ast.Mult, + 'divide': ast.Div, + 'modulo': ast.Mod, + 'power': ast.Pow, + 'and_': ast.And, + 'or_': ast.Or, + 'not_': ast.Not, + 'equals': ast.Eq, + 'less': ast.Lt, + 'greater': ast.Gt} + + def __init__(self, func, return_type=TYPE_OBJECT, arg_descs=None, kwarg_descs=None, + call_afterwards=None, export_me=True): + """ return_type -- the type (from the type hierarchy) that this + Primitive will return + arg_descs, kwarg_descs -- a list of argument descriptions and + a dictionary of keyword argument descriptions. An argument + description can be either an ArgSlot or a ConstantArg. + call_afterwards -- Code to call after this Primitive has been called + (e.g., for updating labels in LogoCode) (not used for creating + AST) + export_me -- True iff this Primitive should be exported to Python + code (the default case) """ + self.func = func + self.return_type = return_type + + if arg_descs is None: + self.arg_descs = [] + else: + self.arg_descs = arg_descs + + if kwarg_descs is None: + self.kwarg_descs = {} + else: + self.kwarg_descs = kwarg_descs + + self.call_afterwards = call_afterwards + self.export_me = export_me + + def copy(self): + """ Return a Primitive object with the same attributes as this one. + Shallow-copy the arg_descs and kwarg_descs attributes. """ + arg_descs_copy = self.arg_descs[:] + if isinstance(self.arg_descs, ArgListDisjunction): + arg_descs_copy = ArgListDisjunction(arg_descs_copy) + return Primitive(self.func, + return_type=self.return_type, + arg_descs=arg_descs_copy, + kwarg_descs=self.kwarg_descs.copy(), + call_afterwards=self.call_afterwards, + export_me=self.export_me) + + def __repr__(self): + return "Primitive(%s -> %s)" % (repr(self.func), str(self.return_type)) + + @property + def __name__(self): + return self.func.__name__ + + def get_name_for_export(self): + """ Return the expression (as a string) that represents this Primitive + in the exported Python code, e.g., 'turtle.forward'. """ + 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_heap(): + func_name = "logo.heap." + elif self.wants_tawindow(): + func_name = "tw." + # get the name of the function directly from the function itself + func_name += self.func.__name__ + return func_name + + def are_slots_filled(self): + """ Return True iff none of the arg_descs or kwarg_descs is an + ArgSlot. """ + for arg_desc in self.arg_descs: + if isinstance(arg_desc, ArgSlot): + return False + for key in self.kwarg_descs: + if isinstance(self.kwarg_descs[key], ArgSlot): + return False + return True + + def fill_slots(self, arguments=None, keywords=None, convert_to_ast=False, + call_my_args=True): + """ Return a copy of this Primitive whose ArgSlots are filled with + the given arguments, turned into ConstantArgs. Call the arguments, + apply their wrappers, and check their types as appropriate. """ + if arguments is None: + arguments = [] + if keywords is None: + keywords = {} + + new_prim = self.copy() + + if isinstance(new_prim.arg_descs, ArgListDisjunction): + slot_list_alternatives = list(new_prim.arg_descs) + else: + slot_list_alternatives = [new_prim.arg_descs] + + # arguments + error = None + filler = None + for slot_list in slot_list_alternatives: + error = None + new_slot_list = [] + filler_list = list(arguments[:]) + for slot in slot_list: + if isinstance(slot, ArgSlot): + filler = filler_list.pop(0) + try: + const = slot.fill(filler, + convert_to_ast=convert_to_ast, + call_my_args=call_my_args) + except TATypeError as error: + break + else: + new_slot_list.append(const) + else: + new_slot_list.append(slot) + if error is None: + new_prim.arg_descs = new_slot_list + break + if error is not None: + raise error + + # keyword arguments + for key in keywords: + kwarg_desc = new_prim.kwarg_descs[key] + if isinstance(kwarg_desc, ArgSlot): + const = kwarg_desc.fill(keywords[key], + convert_to_ast=convert_to_ast, + call_my_args=call_my_args) + new_prim.kwarg_descs[key] = const + + return new_prim + + def get_values_of_filled_slots(self, exportable_only=False): + """ Return the values of all filled argument slots as a list, and + the values of all filled keyword argument slots as a dictionary. + Ignore all un-filled (keyword) argument slots. + exportable_only -- return only exportable values and convert values + to ASTs instead of calling them """ + new_args = [] + for c_arg in self.arg_descs: + if (isinstance(c_arg, ConstantArg) + and (not exportable_only + or export_me(c_arg.value))): + new_args.append(c_arg.get(convert_to_ast=exportable_only)) + new_kwargs = {} + for key in self.kwarg_descs: + if (isinstance(self.kwarg_descs[key], ConstantArg) + and (not exportable_only + or export_me(self.kwarg_descs[key].value))): + new_kwargs[key] = self.kwarg_descs[key].get( + convert_to_ast=exportable_only) + return (new_args, new_kwargs) + + def allow_call_args(self, recursive=False): + """ Set call_args attribute of all argument descriptions to True + recursive -- recursively call allow_call_args on all constant args + that are Primitives """ + for arg_desc in self.arg_descs: + arg_desc.call_arg = True + if (recursive and isinstance(arg_desc, ConstantArg) and + isinstance(arg_desc.value, Primitive)): + arg_desc.value.allow_call_args(recursive=True) + for kwarg_desc in self.kwarg_descs: + kwarg_desc.call_arg = True + if (recursive and isinstance(kwarg_desc, ConstantArg) and + isinstance(kwarg_desc.value, Primitive)): + kwarg_desc.value.allow_call_args(recursive=True) + + 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 is removed. + The active turtle, the Turtles object, the canvas, the LogoCode + object, or the TurtleArtWindow object will be prepended to the + arguments (depending on what this Primitive wants). """ + + # remove the first argument if it is a LogoCode instance + if runtime_args and isinstance(runtime_args[0], LogoCode): + runtime_args = runtime_args[1:] + + if Primitive._DEBUG: + debug_output(repr(self)) + debug_output(" runtime_args: " + repr(runtime_args)) + # fill the ArgSlots with the runtime arguments + new_prim = self.fill_slots(runtime_args, runtime_kwargs, + convert_to_ast=False) + if not new_prim.are_slots_filled(): + raise logoerror("#syntaxerror") + if Primitive._DEBUG: + debug_output(" new_prim.arg_descs: " + repr(new_prim.arg_descs)) + + # extract the actual values from the (now constant) arguments + (new_args, new_kwargs) = new_prim.get_values_of_filled_slots() + if Primitive._DEBUG: + debug_output(" new_args: " + repr(new_args)) + debug_output("end " + repr(self)) + + # what does this primitive want as its first argument? + first_arg = None + if not is_bound_method(new_prim.func): + if new_prim.wants_turtle(): + first_arg = global_objects["turtles"].get_active_turtle() + elif new_prim.wants_turtles(): + first_arg = global_objects["turtles"] + elif new_prim.wants_canvas(): + first_arg = global_objects["canvas"] + elif new_prim.wants_logocode(): + first_arg = global_objects["logo"] + elif new_prim.wants_heap(): + first_arg = global_objects["logo"].heap + elif new_prim.wants_tawindow(): + first_arg = global_objects["window"] + + # execute the actual function + if first_arg is None: + return_value = new_prim.func(*new_args, **new_kwargs) + else: + return_value = new_prim.func(first_arg, *new_args, **new_kwargs) + + if new_prim.call_afterwards is not None: + new_prim.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. """ + + if Primitive._DEBUG: + debug_output(repr(self)) + debug_output(" arg_asts: " + repr(arg_asts)) + new_prim = self.fill_slots(arg_asts, kwarg_asts, convert_to_ast=True) + if not new_prim.are_slots_filled(): + raise PyExportError("not enough arguments") + if Primitive._DEBUG: + debug_output(" new_prim.arg_descs: " + repr(new_prim.arg_descs)) + + # extract the actual values from the (now constant) arguments + (new_arg_asts, new_kwarg_asts) = new_prim.get_values_of_filled_slots( + exportable_only=True) + if Primitive._DEBUG: + debug_output(" new_arg_asts: " + repr(new_arg_asts)) + debug_output("end " + repr(self)) + + # 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 = get_call_ast("range", [num_repetitions]) + # 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: + pos_cond_ast = new_arg_asts[0].args[0] + condition_ast = ast.UnaryOp(op=ast.Not, + operand=pos_cond_ast) + else: + raise PyExportError("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 + + # boxes + elif self == LogoCode.prim_set_box: + target_ast = ast.Subscript(value=BOX_AST, + slice=ast.Index(value=new_arg_asts[0]), ctx=ast.Store) + return ast.Assign(targets=[target_ast], value=new_arg_asts[1]) + elif self == LogoCode.prim_get_box: + return ast.Subscript(value=BOX_AST, + slice=ast.Index(value=new_arg_asts[0]), ctx=ast.Load) + + # action stacks + elif self == LogoCode.prim_define_stack: + return + elif self == LogoCode.prim_invoke_stack: + stack_func = ast.Subscript(value=ACTION_AST, + slice=ast.Index(value=new_arg_asts[0]), ctx=ast.Load) + call_ast = get_call_ast('logo.icall', [stack_func]) + return [call_ast, ast_yield_true()] + + # stop stack + elif self == LogoCode.prim_stop_stack: + return ast.Return() + + # sleep/ wait + elif self == LogoCode.prim_wait: + return [get_call_ast('sleep', new_arg_asts), ast_yield_true()] + + # standard operators + elif self.func.__name__ in Primitive.STANDARD_OPERATORS: + op = Primitive.STANDARD_OPERATORS[self.func.__name__] + # 'divide': prevent unwanted integer division + if self == Primitive.divide: + def _is_float(x): + return get_type(x)[0] == TYPE_FLOAT + if ( not _is_float(new_arg_asts[0]) and + not _is_float(new_arg_asts[1])): + new_arg_asts[0] = get_call_ast('float', [new_arg_asts[0]], + return_type=TYPE_FLOAT) + 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 issubclass(op, ast.boolop): + return ast.BoolOp(op=op, values=[left, right]) + elif issubclass(op, ast.cmpop): + return ast.Compare(left=left, ops=[op], + comparators=[right]) + else: + return ast.BinOp(op=op, left=left, right=right) + + # f(x) + elif self == LogoCode.prim_myfunction: + param_asts = [] + for id_ in ['x', 'y', 'z'][:len(new_arg_asts)-1]: + param_asts.append(ast.Name(id=id_, ctx=ast.Param)) + func_ast = ast_extensions.LambdaWithStrBody( + body_str=new_arg_asts[0].s, args=param_asts) + return get_call_ast(func_ast, new_arg_asts[1:], + return_type=self.return_type) + + # square root + elif self == Primitive.square_root: + return get_call_ast('sqrt', new_arg_asts, new_kwarg_asts, + return_type=self.return_type) + + # random + elif self in (Primitive.random_char, Primitive.random_int): + uniform_ast = get_call_ast('uniform', new_arg_asts) + round_ast = get_call_ast('round', [uniform_ast, ast.Num(n=0)]) + int_ast = get_call_ast('int', [round_ast], return_type=TYPE_INT) + if self == Primitive.random_char: + chr_ast = get_call_ast('chr', [int_ast], return_type=TYPE_CHAR) + return chr_ast + else: + return int_ast + + # identity + elif self == Primitive.identity: + return new_arg_asts[0] + + # constant + elif self == CONSTANTS.get: + return TypedSubscript(value=ast.Name(id='CONSTANTS', ctx=ast.Load), + slice_=ast.Index(value=new_arg_asts[0]), + return_type=self.return_type) + + # group of Primitives or sandwich-clamp block + elif self in (Primitive.group, LogoCode.prim_clamp): + ast_list = [] + for prim in new_arg_asts[0]: + if export_me(prim): + new_ast = value_to_ast(prim) + if isinstance(new_ast, ast.AST): + ast_list.append(new_ast) + return ast_list + + # comment + elif self == Primitive.comment: + if isinstance(new_arg_asts[0], ast.Str): + text = ' ' + str(new_arg_asts[0].s) + else: + text = ' ' + str(new_arg_asts[0]) + return ast_extensions.Comment(text) + + # print + elif self == TurtleArtWindow.print_: + func_name = self.get_name_for_export() + call_ast = get_call_ast(func_name, new_arg_asts) + print_ast = ast.Print(values=new_arg_asts[:1], dest=None, nl=True) + return [call_ast, print_ast] + + # heap + elif self == LogoCode.get_heap: + return TypedName(id_='logo.heap', return_type=self.return_type) + elif self == LogoCode.reset_heap: + target_ast = ast.Name(id='logo.heap', ctx=ast.Store) + value_ast = ast.List(elts=[], ctx=ast.Load) + return ast.Assign(targets=[target_ast], value=value_ast) + + # NORMAL FUNCTION CALL # + + else: + func_name = self.get_name_for_export() + return get_call_ast(func_name, new_arg_asts, new_kwarg_asts, + return_type=self.return_type) + + 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.return_type == other.return_type and + self.arg_descs == other.arg_descs and + self.kwarg_descs == other.kwarg_descs 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_heap(self): + """ Does this Primitive want to get the heap as its first argument? """ + return ((hasattr(self.func, '__self__') and + isinstance(self.func.__self__, list)) or + self.func in list.__dict__.values()) + + 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): + return is_instancemethod(self.func) and self.func.im_class == theClass + + # treat the following methods in a special way when converting the + # Primitive to an AST + + @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(condition): + """ Loop controller for the 'while' block + condition -- Primitive that is evaluated every time through the + loop """ + condition.allow_call_args(recursive=True) + while condition(): + yield True + yield False + + @staticmethod + def controller_until(condition): + """ Loop controller for the 'until' block + condition -- Primitive that is evaluated every time through the + loop """ + condition.allow_call_args(recursive=True) + while not condition(): + 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) + + for desc in self.arg_descs: + if isinstance(desc, ConstantArg): + value = desc.value + if _is_loop_controller(value): + return value + elif isinstance(desc, ArgSlot): + wrapper = desc.wrapper + if _is_loop_controller(wrapper): + return wrapper + + # no controller found + raise PyExportError("found no loop controller for " + repr(self)) + + @staticmethod + def do_nothing(): + pass + + @staticmethod + def identity(arg): + """ Return the argument unchanged """ + return arg + + @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 the first + argument is a tuple of length 2 and the second is None, use the + values in the tuple as arg1 and arg2. """ + if isinstance(arg1, (list, tuple)) and len(arg1) == 2 and arg2 is None: + (arg1, arg2) = arg1 + 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 float(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 square_root(arg1): + """ Return the square root of the argument. If it is a negative + number, raise a NegativeRootError. """ + if arg1 < 0: + raise NegativeRootError(neg_value=arg1) + return sqrt(arg1) + + @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 + + @staticmethod + def equals(arg1, arg2): + """ Return arg1 == arg2 """ + return arg1 == arg2 + + @staticmethod + def less(arg1, arg2): + """ Return arg1 < arg2 """ + return arg1 < arg2 + + @staticmethod + def greater(arg1, arg2): + """ Return arg1 > arg2 """ + return arg1 > arg2 + + @staticmethod + def comment(text): + """ In 'snail' execution mode, display the comment. Else, do nothing. """ + tw = global_objects["window"] + if not tw.hide and tw.step_time != 0: + tw.showlabel('print', text) + + @staticmethod + def random_int(lower, upper): + """ Choose a random integer between lower and upper, which must be + integers """ + return int(round(uniform(lower, upper), 0)) + + @staticmethod + def random_char(lower, upper): + """ Choose a random Unicode code point between lower and upper, + which must be integers """ + return chr(Primitive.random_int(lower, upper)) + + +class Disjunction(tuple): + """ Abstract disjunction class (not to be instantiated directly) """ + + def __init__(self, iterable): + self = tuple(iterable) + + def __repr__(self): + s = ["("] + for disj in self: + s.append(repr(disj)) + s.append(" or ") + s.pop() + s.append(")") + return "".join(s) + + def get_alternatives(self): + """ Return a tuple of alternatives, i.e. self """ + return self + + +class PrimitiveDisjunction(Disjunction,Primitive): + """ Disjunction of two or more Primitives. PrimitiveDisjunctions may not + be nested. """ + + @property + def return_type(self): + """ Tuple of the return_types of all disjuncts """ + return TypeDisjunction((prim.return_type for prim in self)) + + def __call__(self, *runtime_args, **runtime_kwargs): + """ Loop over the disjunct Primitives and try to fill their slots + with the given args and kwargs. Call the first Primitives whose + slots could be filled successfully. If all disjunct Primitives + fail, raise the last error that occurred. """ + + # remove the first argument if it is a LogoCode instance + if runtime_args and isinstance(runtime_args[0], LogoCode): + runtime_args = runtime_args[1:] + + error = None + for prim in self: + try: + new_prim = prim.fill_slots(runtime_args, runtime_kwargs, + convert_to_ast=False) + except TATypeError as error: + # on failure, try the next one + continue + else: + # on success, call this Primitive + return new_prim() + + # if we get here, all disjuncts failed + if error is not None: + raise error + + +class ArgListDisjunction(Disjunction): + """ Disjunction of two or more argument lists """ + pass + + +class ArgSlot(object): + """ Description of the requirements that a Primitive demands from an + argument or keyword argument. An ArgSlot is filled at runtime, based + on the block program structure. """ + + def __init__(self, type_, call_arg=True, wrapper=None): + """ + type_ -- what type of the type hierarchy the argument should have + (after the wrapper has been applied) + call_arg -- if this argument is callable, should it be called and + its return value passed to the parent Primitive (True, the + default), or should it be passed as it is (False)? + wrapper -- a Primitive that is 'wrapped around' the argument before + it gets passed to its parent Primitive. Wrappers can be nested + infinitely. """ + self.type = type_ + self.call_arg = call_arg + self.wrapper = wrapper + + def __repr__(self): + s = ["ArgSlot(type="] + s.append(repr(self.type)) + if not self.call_arg: + s.append(", call=") + s.append(repr(self.call_arg)) + if self.wrapper is not None: + s.append(", wrapper=") + s.append(repr(self.wrapper)) + s.append(")") + return "".join(s) + + def get_alternatives(self): + """ Return a tuple of slot alternatives, i.e. (self, ) """ + return (self, ) + + def fill(self, argument, convert_to_ast=False, call_my_args=True): + """ Try to fill this argument slot with the given argument. Return + a ConstantArg containing the result. If there is a type problem, + raise a TATypeError. """ + if isinstance(argument, ast.AST): + convert_to_ast = True + + # 1. can the argument be called? + (func_disjunction, args) = (None, []) + if (isinstance(argument, tuple) and argument + and callable(argument[0])): + func_disjunction = argument[0] + if len(argument) >= 2 and isinstance(argument[1], LogoCode): + args = argument[2:] + else: + args = argument[1:] + elif callable(argument): + func_disjunction = argument + + # make sure we can loop over func_disjunction + if not isinstance(func_disjunction, PrimitiveDisjunction): + func_disjunction = PrimitiveDisjunction((func_disjunction, )) + + error = None + bad_value = argument # the value that caused the TATypeError + for func in func_disjunction: + error = None + for slot in self.get_alternatives(): + + if isinstance(slot.wrapper, PrimitiveDisjunction): + wrapper_disjunction = slot.wrapper + else: + wrapper_disjunction = PrimitiveDisjunction((slot.wrapper,)) + + for wrapper in wrapper_disjunction: + + # check if the argument can fill this slot (type-wise) + if wrapper is not None: + arg_types = get_type(wrapper)[0] + bad_value = wrapper + elif func is not None: + arg_types = get_type(func)[0] + bad_value = func + else: + arg_types = get_type(argument)[0] + bad_value = argument + converter = None + if not isinstance(arg_types, TypeDisjunction): + arg_types = TypeDisjunction((arg_types, )) + if isinstance(slot.type, TypeDisjunction): + slot_types = slot.type + else: + slot_types = TypeDisjunction((slot.type, )) + for old_type in arg_types: + for new_type in slot_types: + converter = get_converter(old_type, new_type) + if converter is not None: + break + # unable to convert, try next wrapper/ slot/ func + if converter is None: + continue + + # 1. (cont'd) call the argument or pass it on as a callable + called_argument = argument + if func is not None: + func_prim = func + if not isinstance(func_prim, Primitive): + func_prim = Primitive(func_prim, + [ArgSlot(TYPE_OBJECT)] * len(args)) + try: + func_prim = func_prim.fill_slots(args, + convert_to_ast=convert_to_ast, + call_my_args=(slot.call_arg and call_my_args)) + except TATypeError as error: + # on failure, try next wrapper/ slot/ func + bad_value = error.bad_value + continue + if convert_to_ast: + called_argument = func_prim.get_ast() + else: + if slot.call_arg and call_my_args: + # call and pass on the return value + called_argument = func_prim() + else: + # don't call and pass on the callable + called_argument = func_prim + + # 2. apply any wrappers + wrapped_argument = called_argument + if wrapper is not None: + if convert_to_ast: + if not hasattr(wrapper, "get_ast"): + raise PyExportError(("cannot convert callable" + " %s to an AST") % (repr(wrapper))) + wrapped_argument = wrapper.get_ast( + called_argument) + else: + if slot.call_arg and call_my_args: + wrapped_argument = wrapper(called_argument) + else: + wrapped_argument = wrapper.fill_slots( + [called_argument], call_my_args=False) + + # last chance to convert raw values to ASTs + # (but not lists of ASTs) + if (convert_to_ast and + not isinstance(wrapped_argument, ast.AST) and + not (isinstance(wrapped_argument, list) and + wrapped_argument and + isinstance(wrapped_argument[0], ast.AST))): + wrapped_argument = value_to_ast(wrapped_argument) + + # 3. check the type and convert the argument if necessary + converted_argument = wrapped_argument + if slot.call_arg and call_my_args: + try: + converted_argument = convert(wrapped_argument, + new_type, old_type=old_type, + converter=converter) + except TATypeError as error: + # on failure, try next wrapper/ slot/ func + bad_value = wrapped_argument + continue + elif converter != identity: + converted_argument = Primitive(converter, + return_type=new_type, + arg_descs=[ConstantArg(wrapped_argument, + value_type=old_type, call_arg=False)]) + # on success, return the result + return ConstantArg(converted_argument, + value_type=new_type, + call_arg=(slot.call_arg and call_my_args)) + + # if we haven't returned anything yet, then all alternatives failed + if error is not None: + raise error + else: + raise TATypeError(bad_value=bad_value, bad_type=old_type, + req_type=new_type) + + +class ArgSlotDisjunction(Disjunction,ArgSlot): + """ Disjunction of two or more argument slots """ + pass + + +class ConstantArg(object): + """ A constant argument or keyword argument to a Primitive. It is + independent of the block program structure. """ + + def __init__(self, value, call_arg=True, value_type=None): + """ call_arg -- call the value before returning it? + value_type -- the type of the value (from the TA type system). This + is useful to store e.g., the return type of call ASTs. """ + self.value = value + self.call_arg = call_arg + self.value_type = value_type + + def get(self, convert_to_ast=False): + """ If call_arg is True and the value is callable, call the value + and return its return value. Else, return the value unchanged. + convert_to_ast -- return the equivalent AST instead of a raw value """ + if self.call_arg and callable(self.value): + if convert_to_ast: + return value_to_ast(self.value) + else: + return self.value() + else: + if convert_to_ast and not isinstance(self.value, list): + return value_to_ast(self.value) + else: + return self.value + + def get_value_type(self): + """ If this ConstantArg has stored the type of its value, return + that. Else, use get_type(...) to guess the type of the value. """ + if self.value_type is None: + return get_type(self.value)[0] + else: + return self.value_type + + def __repr__(self): + s = ["ConstantArg("] + s.append(repr(self.value)) + if not self.call_arg: + s.append(", call=") + s.append(repr(self.call_arg)) + s.append(")") + return "".join(s) + + +def or_(*disjuncts): + """ Return a disjunction object of the same type as the disjuncts. If + the item type cannot be linked to a Disjunction class, return a tuple + of the disjuncts. """ + if isinstance(disjuncts[0], Primitive): + return PrimitiveDisjunction(disjuncts) + elif isinstance(disjuncts[0], (list, ArgListDisjunction)): + return ArgListDisjunction(disjuncts) + elif isinstance(disjuncts[0], ArgSlot): + return ArgSlotDisjunction(disjuncts) + elif isinstance(disjuncts[0], Type): + return TypeDisjunction(disjuncts) + else: + return tuple(disjuncts) + + +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. """ + # already an AST + if isinstance(value, ast.AST): + return value + # Primitive + elif isinstance(value, Primitive): + if value.export_me: + return value.get_ast(*args_for_prim, **kwargs_for_prim) + else: + return None + # boolean + elif isinstance(value, bool): + return ast.Name(id=str(value), ctx=ast.Load) + # number + elif isinstance(value, (int, float)): + return ast.Num(n=value) + # string + elif isinstance(value, basestring): + return ast.Str(value) + # list (recursively transform to an AST) + 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) + # color + elif isinstance(value, Color): + # call to the Color constructor with this object's values, + # e.g., Color('red', 0, 50, 100) + return get_call_ast('Color', [value.name, value.color, + value.shade, value.gray], + return_type=TYPE_COLOR) + # media + elif isinstance(value, Media): + args = [value_to_ast(value.type), value_to_ast(value.value)] + return get_call_ast('Media', args, return_type=TYPE_MEDIA) + # unknown + else: + raise PyExportError("unknown type of raw value: " + repr(type(value))) + + +def ast_yield_true(): + return ast.Yield(value=ast.Name(id='True', ctx=ast.Load)) + + +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 b87d0a6..68b97cc 100644 --- a/TurtleArt/taturtle.py +++ b/TurtleArt/taturtle.py @@ -28,7 +28,7 @@ import cairo from random import uniform from math import sin, cos, pi, sqrt from taconstants import (TURTLE_LAYER, DEFAULT_TURTLE_COLORS, DEFAULT_TURTLE, - COLORDICT) + Color) from tasprite_factory import SVG, svg_str_to_pixbuf from tacanvas import wrap100, COLOR_TABLE from sprites import Sprite @@ -347,13 +347,7 @@ class Turtle: def set_heading(self, heading, share=True): ''' Set the turtle heading (one shape per 360/SHAPES degrees) ''' - try: - self._heading = heading - except (TypeError, ValueError): - debug_output('bad value sent to %s' % (__name__), - self._turtles.turtle_window.running_sugar) - return - self._heading %= 360 + self._heading = heading % 360 self._update_sprite_heading() @@ -373,25 +367,20 @@ class Turtle: def set_color(self, color=None, share=True): ''' Set the pen color for this turtle. ''' + if color is None: + color = self._pen_color # Special case for color blocks - if color is not None and color in COLORDICT: - self.set_shade(COLORDICT[color][1], share) - self.set_gray(COLORDICT[color][2], share) - if COLORDICT[color][0] is not None: - self.set_color(COLORDICT[color][0], share) - color = COLORDICT[color][0] + elif isinstance(color, Color): + self.set_shade(color.shade, share) + self.set_gray(color.gray, share) + if color.color is not None: + color = color.color else: color = self._pen_color - elif color is None: - color = self._pen_color - try: - self._pen_color = color - except (TypeError, ValueError): - debug_output('bad value sent to %s' % (__name__), - self._turtles.turtle_window.running_sugar) - return + self._pen_color = color + # TODO replace these three attributes with one reference to a Color self._turtles.turtle_window.canvas.set_fgcolor(shade=self._pen_shade, gray=self._pen_gray, color=self._pen_color) @@ -404,12 +393,7 @@ class Turtle: def set_gray(self, gray=None, share=True): ''' Set the pen gray level for this turtle. ''' if gray is not None: - try: - self._pen_gray = gray - except (TypeError, ValueError): - debug_output('bad value sent to %s' % (__name__), - self._turtles.turtle_window.running_sugar) - return + self._pen_gray = gray if self._pen_gray < 0: self._pen_gray = 0 @@ -428,12 +412,7 @@ class Turtle: def set_shade(self, shade=None, share=True): ''' Set the pen shade for this turtle. ''' if shade is not None: - try: - self._pen_shade = shade - except (TypeError, ValueError): - debug_output('bad value sent to %s' % (__name__), - self._turtles.turtle_window.running_sugar) - return + self._pen_shade = shade self._turtles.turtle_window.canvas.set_fgcolor(shade=self._pen_shade, gray=self._pen_gray, @@ -447,12 +426,7 @@ class Turtle: def set_pen_size(self, pen_size=None, share=True): ''' Set the pen size for this turtle. ''' if pen_size is not None: - try: - self._pen_size = max(0, pen_size) - except (TypeError, ValueError): - debug_output('bad value sent to %s' % (__name__), - self._turtles.turtle_window.running_sugar) - return + self._pen_size = max(0, pen_size) self._turtles.turtle_window.canvas.set_pen_size( self._pen_size * self._turtles.turtle_window.coord_scale) @@ -570,7 +544,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/tatype.py b/TurtleArt/tatype.py new file mode 100644 index 0000000..78cbcc3 --- /dev/null +++ b/TurtleArt/tatype.py @@ -0,0 +1,439 @@ +#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. + +""" type system for Primitives and their arguments """ + +import ast + +from tablock import Media +from taconstants import (Color, CONSTANTS) +from tautils import debug_output + + +class Type(object): + """ A type in the type hierarchy. """ + + def __init__(self, constant_name, value): + """ constant_name -- the name of the constant that points to this Type + object + value -- an arbitrary integer that is different from the values of + all other Types. The order of the integers doesn't matter. """ + self.constant_name = constant_name + self.value = value + + def __eq__(self, other): + if other is None: + return False + if not isinstance(other, Type): + return False + return self.value == other.value + + def __str__(self): + return str(self.constant_name) + __repr__ = __str__ + + +class TypeDisjunction(tuple,Type): + """ Disjunction of two or more Types (from the type hierarchy) """ + + def __init__(self, iterable): + self = tuple(iterable) + + def __str__(self): + s = ["("] + for disj in self: + s.append(str(disj)) + s.append(" or ") + s.pop() + s.append(")") + return "".join(s) + + +# individual types +TYPE_OBJECT = Type('TYPE_OBJECT', 0) +TYPE_BOOL = Type('TYPE_BOOL', 5) +TYPE_BOX = Type('TYPE_BOX', 8) # special type for the unknown content of a box +TYPE_CHAR = Type('TYPE_CHAR', 1) +TYPE_COLOR = Type('TYPE_COLOR', 2) +TYPE_FLOAT = Type('TYPE_FLOAT', 3) +TYPE_INT = Type('TYPE_INT', 4) +TYPE_MEDIA = Type('TYPE_MEDIA', 10) +TYPE_NUMBER = Type('TYPE_NUMBER', 6) # shortcut to avoid a TypeDisjunction + # between TYPE_FLOAT and TYPE_INT +TYPE_NUMERIC_STRING = Type('TYPE_NUMERIC_STRING', 7) +TYPE_STRING = Type('TYPE_STRING', 9) + +# groups/ classes of types +TYPES_NUMERIC = (TYPE_FLOAT, TYPE_INT, TYPE_NUMBER) + + +BOX_AST = ast.Name(id='BOX', ctx=ast.Load) +ACTION_AST = ast.Name(id='ACTION', ctx=ast.Load) + + +def get_type(x): + """ Return the most specific type in the type hierarchy that applies to x + and a boolean indicating whether x is an AST. If the type cannot be + determined, return TYPE_OBJECT as the type. """ + # non-AST types + if isinstance(x, (int, long)): + return (TYPE_INT, False) + elif isinstance(x, float): + return (TYPE_FLOAT, False) + elif isinstance(x, basestring): + if len(x) == 1: + return (TYPE_CHAR, False) + try: + float(x) + except ValueError: + return (TYPE_STRING, False) + else: + return (TYPE_NUMERIC_STRING, False) + elif isinstance(x, Color): + return (TYPE_COLOR, False) + elif isinstance(x, Media): + return (TYPE_MEDIA, False) + elif hasattr(x, "return_type"): + return (x.return_type, False) + + # AST types + elif isinstance(x, ast.Num): + return (get_type(x.n)[0], True) + elif isinstance(x, ast.Str): + return (get_type(x.s)[0], True) + elif isinstance(x, ast.Name): + try: + # we need to have imported CONSTANTS for this to work + value = eval(x.id) + except NameError: + return (TYPE_OBJECT, True) + else: + return (get_type(value)[0], True) + elif isinstance(x, ast.Subscript): + if x.value == BOX_AST: + return (TYPE_BOX, True) + elif isinstance(x, ast.Call): + if isinstance(x.func, ast.Name): + if x.func.id == 'float': + return (TYPE_FLOAT, True) + elif x.func.id in ('int', 'ord'): + return (TYPE_INT, True) + elif x.func.id == 'chr': + return (TYPE_CHAR, True) + elif x.func.id in ('repr', 'str', 'unicode'): + return (TYPE_STRING, True) + elif x.func.id == 'Color': + return (TYPE_COLOR, True) + elif x.func.id == 'Media': + return (TYPE_MEDIA, True) + # unary operands never change the type of their argument + elif isinstance(x, ast.UnaryOp): + if issubclass(x.op, ast.Not): + # 'not' always returns a boolean + return (TYPE_BOOL, True) + else: + return get_type(x.operand) + # boolean and comparison operators always return a boolean + if isinstance(x, (ast.BoolOp, ast.Compare)): + return (TYPE_BOOL, True) + # other binary operators + elif isinstance(x, ast.BinOp): + type_left = get_type(x.left)[0] + type_right = get_type(x.right)[0] + if type_left == TYPE_STRING or type_right == TYPE_STRING: + return (TYPE_STRING, True) + if type_left == type_right == TYPE_INT: + return (TYPE_INT, True) + else: + return (TYPE_FLOAT, True) + + return (TYPE_OBJECT, isinstance(x, ast.AST)) + + +def is_instancemethod(method): + # TODO how to access the type `instancemethod` directly? + return type(method).__name__ == "instancemethod" + +def is_bound_method(method): + return ((is_instancemethod(method) and method.im_self is not None) or + (hasattr(method, '__self__') and method.__self__ is not None)) + +def is_staticmethod(method): + # TODO how to access the type `staticmethod` directly? + return type(method).__name__ == "staticmethod" + + +def identity(x): + return x + +TYPE_CONVERTERS = { + # Type hierarchy: If there is a converter A -> B, then A is a subtype of B. + # The converter from A to B is stored under TYPE_CONVERTERS[A][B]. + # The relation describing the type hierarchy must be transitive, i.e. + # converting A -> C must yield the same result as converting A -> B -> C. + # TYPE_OBJECT is the supertype of everything. + TYPE_BOX: { + TYPE_FLOAT: float, + TYPE_INT: int, + TYPE_NUMBER: float, + TYPE_STRING: str}, + TYPE_CHAR: { + TYPE_INT: ord, + TYPE_STRING: identity}, + TYPE_COLOR: { + TYPE_FLOAT: float, + TYPE_INT: int, + TYPE_NUMBER: int, + TYPE_STRING: Color.get_number_string}, + TYPE_FLOAT: { + TYPE_INT: int, + TYPE_NUMBER: identity, + TYPE_STRING: str}, + TYPE_INT: { + TYPE_FLOAT: float, + TYPE_NUMBER: identity, + TYPE_STRING: str}, + TYPE_NUMBER: { + TYPE_FLOAT: float, + TYPE_INT: int, + TYPE_STRING: str}, + TYPE_NUMERIC_STRING: { + TYPE_FLOAT: float, + TYPE_STRING: identity} +} + + +class TATypeError(BaseException): + """ TypeError with the types from the hierarchy, not with Python types """ + + def __init__(self, bad_value, bad_type=None, req_type=None, message=''): + """ bad_value -- the mis-typed value that caused the error + bad_type -- the type of the bad_value + req_type -- the type that the value was expected to have + message -- short statement about the cause of the error. It is + not shown to the user, but may appear in debugging output. """ + self.bad_value = bad_value + self.bad_type = bad_type + self.req_type = req_type + self.message = message + + def __str__(self): + msg = [] + if self.message: + msg.append(self.message) + msg.append(" (") + msg.append("bad value: ") + msg.append(repr(self.bad_value)) + if self.bad_type is not None: + msg.append(", bad type: ") + msg.append(repr(self.bad_type)) + if self.req_type is not None: + msg.append(", req type: ") + msg.append(repr(self.req_type)) + if self.message: + msg.append(")") + return "".join(msg) + __repr__ = __str__ + + +def get_converter(old_type, new_type): + """ If there is a converter old_type -> new_type, return it. Else return + None. If a chain of converters is necessary, return it as a tuple or + list (starting with the innermost, first-to-apply converter). """ + # every type can be converted to TYPE_OBJECT + if new_type == TYPE_OBJECT: + return identity + # every type can be converted to itself + if old_type == new_type: + return identity + + # is there a converter for this pair of types? + converters_from_old = TYPE_CONVERTERS.get(old_type) + if converters_from_old is None: + return None + converter = converters_from_old.get(new_type) + if converter is not None: + return converter + else: + # form the transitive closure of all types that old_type can be + # converted to, and look for new_type there + backtrace = converters_from_old.copy() + new_backtrace = backtrace.copy() + break_all = False + while True: + newest_backtrace = {} + for t in new_backtrace: + for new_t in TYPE_CONVERTERS.get(t, {}): + if new_t not in backtrace: + newest_backtrace[new_t] = t + backtrace[new_t] = t + if new_t == new_type: + break_all = True + break + if break_all: + break + if break_all or not newest_backtrace: + break + new_backtrace = newest_backtrace + # use the backtrace to find the path from old_type to new_type + if new_type in backtrace: + converter_chain = [] + t = new_type + while t in backtrace and isinstance(backtrace[t], Type): + converter_chain.insert(0, TYPE_CONVERTERS[backtrace[t]][t]) + t = backtrace[t] + converter_chain.insert(0, TYPE_CONVERTERS[old_type][t]) + return converter_chain + return None + + +def convert(x, new_type, old_type=None, converter=None): + """ Convert x to the new type if possible. + old_type -- the type of x. If not given, it is computed. """ + if not isinstance(new_type, Type): + raise ValueError('%s is not a type in the type hierarchy' + % (repr(new_type))) + # every type can be converted to TYPE_OBJECT + if new_type == TYPE_OBJECT: + return x + if not isinstance(old_type, Type): + (old_type, is_an_ast) = get_type(x) + else: + is_an_ast = isinstance(x, ast.AST) + # every type can be converted to itself + if old_type == new_type: + return x + + # special case: 'box' block (or 'pop' block) as an AST + if is_an_ast and old_type == TYPE_BOX: + new_type_ast = ast.Name(id=new_type.constant_name) + return get_call_ast('convert', [x, new_type_ast], return_type=new_type) + + # if the converter is not given, try to find one + if converter is None: + converter = get_converter(old_type, new_type) + if converter is None: + # no converter available + raise TATypeError(bad_value=x, bad_type=old_type, + req_type=new_type, message=("found no converter" + " for this type combination")) + + def _apply_converter(converter, y): + if is_an_ast: + if converter == identity: + return y + elif is_instancemethod(converter): + func = ast.Attribute(value=y, + attr=converter.im_func.__name__, + ctx=ast.Load) + return get_call_ast(func) + else: + func_name = converter.__name__ + return get_call_ast(func_name, [y]) + else: + return converter(y) + + if isinstance(converter, (list, tuple)): + # apply the converter chain recursively + result = x + for conv in converter: + result = _apply_converter(conv, result) + return result + elif converter is not None: + return _apply_converter(converter, x) + + +class TypedAST(ast.AST): + + @property + def return_type(self): + if self._return_type is None: + return get_type(self.func)[0] + else: + return self._return_type + + +class TypedCall(ast.Call,TypedAST): + """ Like a Call AST, but with a return type """ + + def __init__(self, func, args=None, keywords=None, starargs=None, + kwargs=None, return_type=None): + + if args is None: + args = [] + if keywords is None: + keywords = [] + + ast.Call.__init__(self, func=func, args=args, keywords=keywords, + starargs=starargs, kwargs=kwargs) + + self._return_type = return_type + + +class TypedSubscript(ast.Subscript,TypedAST): + """ Like a Subscript AST, but with a type """ + + def __init__(self, value, slice_, ctx=ast.Load, return_type=None): + + ast.Subscript.__init__(self, value=value, slice=slice_, ctx=ctx) + + self._return_type = return_type + + +class TypedName(ast.Name,TypedAST): + """ Like a Name AST, but with a type """ + + def __init__(self, id_, ctx=ast.Load, return_type=None): + + ast.Name.__init__(self, id=id_, ctx=ctx) + + self._return_type = return_type + + +def get_call_ast(func_name, args=None, kwargs=None, return_type=None): + """ Return an AST representing the call to a function with the name + func_name, passing it the arguments args (given as a list) and the + keyword arguments kwargs (given as a dictionary). + func_name -- either the name of a callable as a string, or an AST + representing a callable expression + return_type -- if this is not None, return a TypedCall object with this + return type instead """ + if args is None: + args = [] + # convert keyword argument dict to a list of (key, value) pairs + keywords = [] + if kwargs is not None: + for (key, value) in kwargs.iteritems(): + keywords.append(ast.keyword(arg=key, value=value)) + # get or generate the AST representing the callable + if isinstance(func_name, ast.AST): + func_ast = func_name + else: + func_ast = ast.Name(id=func_name, ctx=ast.Load) + # if no return type is given, return a simple Call AST + if return_type is None: + return ast.Call(func=func_ast, args=args, keywords=keywords, + starargs=None, kwargs=None) + # if a return type is given, return a TypedCall AST + else: + return TypedCall(func=func_ast, args=args, keywords=keywords, + return_type=return_type) + + diff --git a/TurtleArt/tautils.py b/TurtleArt/tautils.py index b0aa368..634ee01 100644 --- a/TurtleArt/tautils.py +++ b/TurtleArt/tautils.py @@ -107,7 +107,7 @@ def chr_to_ord(x): ''' Try to comvert a string to an ord ''' if strtype(x) and len(x) == 1: try: - return ord(x[0]), True + return ord(x), True except ValueError: return x, False return x, False @@ -115,9 +115,7 @@ def chr_to_ord(x): def strtype(x): ''' Is x a string type? ''' - if isinstance(x, (str, unicode)): - return True - return False + return isinstance(x, basestring) def increment_name(name): @@ -311,7 +309,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] @@ -808,6 +806,30 @@ def find_blk_below(blk, namelist): return None +def get_stack_name(blk): + ''' Return the name of the action stack that the given block belongs to. + If the top block of this stack is not a stack-defining block, return + None. ''' + top_block = find_top_block(blk) + if top_block.name == 'start': + return 'start' + elif top_block.name == 'hat1': + return 'stack1' + elif top_block.name == 'hat2': + return 'stack2' + elif top_block.name == 'hat': + try: + return top_block.connections[1].values[0] + except (AttributeError, TypeError, IndexError): + # AttributeError: t_b has no attribute 'connections' or t_b.c[1] + # has no attribute 'value' + # TypeError: t_b.c or t_b.c[1].v is not a subscriptable sequence + # IndexError: t_b.c or t_b.c[1].v is too short + return None + else: + return None + + def get_hardware(): ''' Determine whether we are using XO 1.0, 1.5, ... or 'unknown' hardware ''' diff --git a/TurtleArt/tawindow.py b/TurtleArt/tawindow.py index af611a3..b01832c 100644 --- a/TurtleArt/tawindow.py +++ b/TurtleArt/tawindow.py @@ -58,7 +58,7 @@ from taconstants import (HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE, 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, TMP_SVG_PATH) + EXPANDABLE_FLOW, SUFFIX, TMP_SVG_PATH, Color) from tapalette import (palette_names, palette_blocks, expandable_blocks, block_names, content_blocks, default_values, special_names, block_styles, help_strings, @@ -67,7 +67,7 @@ from tapalette import (palette_names, palette_blocks, expandable_blocks, palette_init_on_start) from talogo import (LogoCode, primitive_dictionary, logoerror) from tacanvas import TurtleGraphics -from tablock import (Blocks, Block) +from tablock import (Blocks, Block, Media, media_blocks_dictionary) from taturtle import (Turtles, Turtle) from tautils import (magnitude, get_load_name, get_save_name, data_from_file, data_to_file, round_int, get_id, get_pixbuf_from_journal, @@ -77,7 +77,7 @@ from tautils import (magnitude, get_load_name, get_save_name, data_from_file, 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_hat, find_bot_block, + error_output, find_hat, find_bot_block, restore_clamp, collapse_clamp, data_from_string, increment_name, get_screen_dpi) from tasprite_factory import (SVG, svg_str_to_pixbuf, svg_from_file) @@ -96,6 +96,9 @@ _PLUGIN_SUBPATH = 'plugins' _MACROS_SUBPATH = 'macros' +global_objects = {} # the global instances of single-instance classes + + class TurtleArtWindow(): ''' TurtleArt Window class abstraction ''' @@ -300,6 +303,11 @@ class TurtleArtWindow(): from tabasics import Palettes self._basic_palettes = Palettes(self) + global_objects["window"] = self + global_objects["canvas"] = self.canvas + global_objects["logo"] = self.lc + global_objects["turtles"] = self.turtles + if self.interactive_mode: gobject.idle_add(self._lazy_init) else: @@ -406,7 +414,7 @@ class TurtleArtWindow(): for plugin in self.turtleart_plugins: plugin.setup() - def _start_plugins(self): + def start_plugins(self): ''' Start is called everytime we execute blocks. ''' for plugin in self.turtleart_plugins: plugin.start() @@ -724,6 +732,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') @@ -3123,7 +3134,7 @@ before making changes to your program')) if self.canvas.cr_svg is None: self.canvas.setup_svg_surface() self.running_blocks = True - self._start_plugins() # Let the plugins know we are running. + self.start_plugins() # Let the plugins know we are running. top = find_top_block(blk) code = self.lc.generate_code(top, self.just_blocks()) self.lc.run_blocks(code) @@ -4306,6 +4317,60 @@ before making changes to your program')) elif self.interactive_mode: self.parent.set_title(text) + def print_(self, n, flag): + """ Print object n to the bar at the bottom of the screen """ + if flag and (self.hide or self.step_time == 0): + return + + # list + if isinstance(n, list): + heap_as_string = str(self.lc.heap) + if len(heap_as_string) > 80: + self.showlabel('print', str(self.lc.heap)[0:79] + '…') + else: + self.showlabel('print', str(self.lc.heap)) + # color + elif isinstance(n, Color): + if n.color is None: + self.showlabel('print', '%s %d, %s %d' % + (_('shade'), n.shade, + _('gray'), n.gray)) + else: + self.showlabel('print', '%s %d, %s %d, %s %d' % + (_('color'), n.color, + _('shade'), n.shade, + _('gray'), n.gray)) + # media + elif isinstance(n, Media): + if (n.type == 'media' and + n.value.lower() not in media_blocks_dictionary): + try: + if self.running_sugar: + from sugar.datastore import datastore + try: + dsobject = datastore.get(n.value) + except: + debug_output("Couldn't open %s" % (n.value), + self.running_sugar) + self.showlabel('print', dsobject.metadata['title']) + dsobject.destroy() + else: + self.showlabel('print', n.value) + except IOError: + self.showlabel('print', str(n)) + else: + self.showlabel('print', str(n)) + # string + elif isinstance(n, basestring): + self.showlabel('print', n) + # integer + elif isinstance(n, int): + self.showlabel('print', n) + # other number + else: + self.showlabel( + 'print', + str(round_int(n)).replace('.', self.decimal_point)) def showlabel(self, shp, label=''): ''' Display a message on a status block ''' @@ -4575,7 +4640,6 @@ before making changes to your program')) palette = make_palette('blocks') # Create a new block prototype. - primitive_dictionary['stack'] = self._prim_stack palette.add_block('stack_%s' % (name), style='basic-style-1arg', label=name, @@ -4584,7 +4648,6 @@ before making changes to your program')) logo_command='action', default=name, help_string=_('invokes named action stack')) - self.lc.def_prim('stack', 1, primitive_dictionary['stack'], True) # Regenerate the palette, which will now include the new block. self.show_toolbar_palette(palette_name_to_index('blocks'), @@ -4604,7 +4667,6 @@ before making changes to your program')) palette = make_palette('blocks') # Create a new block prototype. - primitive_dictionary['box'] = self._prim_box palette.add_block('box_%s' % (name), style='number-style-1strarg', label=name, @@ -4613,8 +4675,6 @@ before making changes to your program')) default=name, logo_command='box', help_string=_('named variable (numeric value)')) - self.lc.def_prim('box', 1, - lambda self, x: primitive_dictionary['box'](x)) # Regenerate the palette, which will now include the new block. self.show_toolbar_palette(palette_name_to_index('blocks'), @@ -4634,7 +4694,6 @@ before making changes to your program')) palette = make_palette('blocks') # Create a new block prototype. - primitive_dictionary['setbox'] = self._prim_setbox palette.add_block('storein_%s' % (name), style='basic-style-2arg', label=[_('store in'), name, _('value')], @@ -4644,52 +4703,11 @@ before making changes to your program')) default=[name, 100], help_string=_('stores numeric value in named \ variable')) - self.lc.def_prim( - 'storeinbox', - 2, - lambda self, x, y: primitive_dictionary['setbox']('box3', x, y)) # Regenerate the palette, which will now include the new block. self.show_toolbar_palette(palette_name_to_index('blocks'), regenerate=True) - def _prim_stack(self, x): - ''' Process a named stack ''' - if isinstance(convert(x, float, False), float): - if int(float(x)) == x: - x = int(x) - if 'stack3' + str(x) not in self.lc.stacks or \ - self.lc.stacks['stack3' + str(x)] is None: - raise logoerror('#nostack') - self.lc.icall(self.lc.evline, - self.lc.stacks['stack3' + str(x)][:]) - yield True - self.lc.procstop = False - self.lc.ireturn() - yield True - - def _prim_box(self, x): - ''' Retrieve value from named box ''' - if isinstance(convert(x, float, False), float): - if int(float(x)) == x: - x = int(x) - try: - return self.lc.boxes['box3' + str(x)] - except KeyError: - raise logoerror('#emptybox') - - def _prim_setbox(self, name, x, val): - ''' Define value of named box ''' - if x is not None: - if isinstance(convert(x, float, False), float): - if int(float(x)) == x: - x = int(x) - self.lc.boxes[name + str(x)] = val - self.lc.update_label_value('box', val, label=x) - else: - self.lc.boxes[name] = val - self.lc.update_label_value(name, val) - def dock_dx_dy(self, block1, dock1n, block2, dock2n): ''' Find the distance between the dock points of two blocks. ''' # Cannot dock a block to itself diff --git a/TurtleArtActivity.py b/TurtleArtActivity.py index 078377b..5e32f41 100644 --- a/TurtleArtActivity.py +++ b/TurtleArtActivity.py @@ -66,6 +66,7 @@ from TurtleArt.tapalette import (palette_names, help_strings, help_palettes, from TurtleArt.taconstants import (BLOCK_SCALE, XO1, XO15, XO175, XO4, MIMETYPE) from TurtleArt.taexportlogo import save_logo +from TurtleArt.taexportpython import save_python from TurtleArt.tautils import (data_to_file, data_to_string, data_from_string, get_path, chooser_dialog, get_hardware) from TurtleArt.tawindow import TurtleArtWindow @@ -206,6 +207,26 @@ class TurtleArtActivity(activity.Activity): gobject.timeout_add(250, self.save_as_logo.set_icon, 'logo-saveoff') self._notify_successful_save(title=_('Save as Logo')) + def do_save_as_python_cb(self, button): + ''' Write Python code to datastore. ''' + self.save_as_python.set_icon('python-saveon') + py_code_path = self._dump_python_code() + if py_code_path is None: + return + + dsobject = datastore.create() + dsobject.metadata['title'] = self.metadata['title'] + '.py' + dsobject.metadata['mime_type'] = 'text/plain' + dsobject.metadata['icon-color'] = profile.get_color().to_string() + dsobject.set_file_path(py_code_path) + datastore.write(dsobject) + dsobject.destroy() + + os.remove(py_code_path) + gobject.timeout_add(250, self.save_as_python.set_icon, + 'python-saveoff') + self._notify_successful_save(title=_('Save as Python')) + def do_load_ta_project_cb(self, button, new=False): ''' Load a project from the Journal. ''' self._create_new = new @@ -558,6 +579,23 @@ class TurtleArtActivity(activity.Activity): tmpfile = None return tmpfile + def _dump_python_code(self): + ''' Save Python code to temporary file. ''' + datapath = get_path(activity, 'instance') + tmpfile = os.path.join(datapath, 'tmpfile.py') + code = save_python(self.tw) + if len(code) == 0: + _logger.debug('save_python returned None') + return None + try: + f = file(tmpfile, 'w') + f.write(code) + f.close() + except Exception, e: + _logger.error("Couldn't save Python code: " + str(e)) + tmpfile = None + return tmpfile + def _dump_ta_code(self): ''' Save TA code to temporary file. ''' datapath = get_path(activity, 'instance') @@ -804,6 +842,7 @@ class TurtleArtActivity(activity.Activity): add_section(help_box, _('Save/Load'), icon='turtleoff') add_paragraph(help_box, _('Save as image'), icon='image-saveoff') add_paragraph(help_box, _('Save as Logo'), icon='logo-saveoff') + add_paragraph(help_box, _('Save as Python'), icon='python-saveoff') add_paragraph(help_box, _('Load project'), icon='load-from-journal') home = os.environ['HOME'] if activity.get_bundle_path()[0:len(home)] == home: @@ -988,6 +1027,9 @@ class TurtleArtActivity(activity.Activity): self.save_as_logo, label = self._add_button_and_label( 'logo-saveoff', _('Save as Logo'), self.do_save_as_logo_cb, None, button_box) + self.save_as_python, label = self._add_button_and_label( + 'python-saveoff', _('Save as Python'), + self.do_save_as_python_cb, None, button_box) # When screen is in portrait mode, the buttons don't fit # on the main toolbar, so put them here. @@ -1024,6 +1066,9 @@ class TurtleArtActivity(activity.Activity): self.save_as_logo = self._add_button( 'logo-saveoff', _('Save as Logo'), self.do_save_as_logo_cb, toolbar) + self.save_as_python = self._add_button( + 'python-saveoff', _('Save as Python'), + self.do_save_as_python_cb, toolbar) self.keep_button = self._add_button( 'filesaveoff', _('Save snapshot'), self.do_keep_cb, toolbar) self.load_ta_project = self._add_button( diff --git a/doc/primitives-with-arguments.md b/doc/primitives-with-arguments.md new file mode 100644 index 0000000..2237898 --- /dev/null +++ b/doc/primitives-with-arguments.md @@ -0,0 +1,125 @@ +How to define Primitive objects for blocks with arguments +========================================================= + +The tutorials in this document assume that the reader is able to +add simple blocks without arguments to Turtle Art. Please refer +to the module documentation of ../TurtleArt/tabasics.py for a +tutorial on that. + +Example 1: Block with one Argument +---------------------------------- + +In this example, we define the `Primitive` object for a block +that increases the pen color by a numeric argument that comes +from another block. In Turtle Art, the block looks like this: + + ,---.___,---------. + / | + | increment color |= + \ | + `---.___,---------´ + +When the block is executed, we want it to do the same as the +following statement: + + Turtle.set_pen_color(plus(Turtle.get_pen_color(), ...)) + +where `...` stands for the output of the block connected to the +right hand dock of our block. For arguments not known in +advance, we need to insert a placeholder in the form of an +`ArgSlot` object. An `ArgSlot` object describes some properties +of the argument it receives. It defines the type of the +argument, it knows whether the argument needs to be called (if +it is callable), and it knows which callable (if any) it must +wrap around the argument before consuming it. (For more on slot +wrappers, please refer to the other examples below.) For this +example, we can use the default values for the second and third +property (`True` and `None`, respectively). We only need to +state the first one, the argument type, explicitly: + + prim_inc_color = Primitive(Turtle.set_pen_color, + arg_descs=[ConstantArg(Primitive( + Primitive.plus, return_type=TYPE_NUMBER, + arg_descs=[ConstantArg(Primitive( + Turtle.get_pen_color, return_type=TYPE_NUMBER)), + ArgSlot(TYPE_NUMBER)]))]) + + self.tw.lc.def_prim('inc_color', 0, prim_inc_color) + +Turtle Art uses the same type system for argument types as for +the return types of Primitive objects. If a value block (such as +the number block) is attached to the right hand dock of the +'increment color' block, then Turtle Art matches the value of +that block against the type requirement of the argument slot. If +the attached block has a Primitive object (such as the 'plus' +block), then that Primitive's return value is matched against +the required type. If Turtle Art doesn't know how to convert the +attached value to the required type, it shows the user an error +message during execution. + + +Example 2: Block with a Slot Wrapper +------------------------------------ + +In Turtle Art, moving the turtle backward by x is the same as +moving it forward by negative x (or -x). In fact, the 'back' +block uses the same method (`Turtle.forward`) as the 'forward' +block. But the 'back' block needs to switch the sign of its +argument before passing it to `Turtle.forward`. I.e. it needs to +execute the following statement: + + Turtle.forward(minus(...)) + +where `...` again stands for the output of the block connected +to the right hand dock of the 'back' block. This is where slot +wrappers come in helpful. A slot wrapper is a Primitive that is +'wrapped around' an argument of its 'parent' Primitive. Slot +wrappers can only be attached to `ArgSlot` objects, that is, to +arguments that come from other blocks. In the case of the 'back' +block, this looks as follows: + + Primitive(Turtle.forward, + arg_descs=[ArgSlot(TYPE_NUMBER, + wrapper=Primitive( + Primitive.minus, return_type=TYPE_NUMBER, + arg_descs=[ArgSlot(TYPE_NUMBER)]))], + call_afterwards=self.after_move)) + +When the 'back' block is called, it passes the argument that it +gets from its right hand dock to the `ArgSlot` object. That, in +turn, passes it to its wrapper, and then matches the type of the +return value of the wrapper against its type requirement. If the +types match, the wrapper's return value is passed back to the +function of the main Primitive, `Turtle.forward`. + +Note that slot wrappers and Primitive objects can be nested +inside each other infinitely deeply. + + +Example 3: Block with a Group of Primitives +------------------------------------------- + +Blocks like the 'clean' block need to do several things in a +row. E.g., the 'clean' block needs to tell the plugins that the +screen is being cleared, it needs to stop media execution, clear +the screen, and reset all turtles. It takes no block arguments, +so it looks like this in Turtle Art: + + ,---.___,---. + / \ + | clean | + \ / + `---.___,---´ + +To execute a series of several Primitives, we need to define a +'group' of Primitives. This 'group' is itself a Primitive, using +the special function `Primitive.group`. When called, it loops +over its arguments and calls them successively. The Primitive +object for the 'clean' block looks like this: + + Primitive(Primitive.group, arg_descs=[ConstantArg([ + Primitive(self.tw.clear_plugins), + Primitive(self.tw.lc.prim_clear_helper, + export_me=False), + Primitive(self.tw.canvas.clearscreen), + Primitive(self.tw.turtles.reset_turtles)])]) diff --git a/doc/type-system.md b/doc/type-system.md new file mode 100644 index 0000000..4615dd1 --- /dev/null +++ b/doc/type-system.md @@ -0,0 +1,116 @@ +The TA Type System +================== + +Why do we Need a Type System? +----------------------------- + +The purpose of the type system is to have a consistent and +standardized way of type-checking and type-converting the +arguments of blocks. For example, the 'minus' block takes two +arguments of type TYPE_NUMBER. But that doesn't mean that only +number blocks can be attached to its argument docks. In fact, +colors, single characters, and numeric strings (like `"-5.2"`) +can easily be converted to numbers. The type system takes care +of this. When e.g., a color is attached to the argument dock of +the 'minus' block, the type system tries to find a converter +from the type TYPE_COLOR (the type of the color block) to the +type TYPE_NUMBER. If it finds one (and in this case it does), +then the converter is applied to the argument. A converter is +simply a callable (usually a function) and applying it simply +means calling it and passing it the value of the argument block +as a parameter. The converter returns the number that cor- +responds to the color, and the number is passed on to the +'minus' block. This way, the 'minus' block does not have to know +about colors, characters, or numeric strings. Likewise, the +color block also does not have to care about how its value can +be converted to a number. + +Why do some Blocks Need Return Types? +------------------------------------- + +The argument to the 'minus' block (to continue our example) need +not be a simple value block; it can also be the result of a +complex mathematical operation, i.e. the return type of another +block such as 'multiply'. The 'minus' block still demands a +value of type TYPE_NUMBER, so the 'multiply' block must provide +information about its return type. This is why blocks that can +be used as arguments to other blocks must specify a return type. + +What if I want to Specify Two Types at the Same Time? +----------------------------------------------------- + +You can use the function `or_` (defined in `taprimitive.py`) to +create disjunctions of `Primitive`s, argument lists, `ArgSlot`s, +or types. Simply pass the disjuncts to it as its arguments. +E.g., to create a disjunction between the types TYPE_NUMBER and +TYPE_STRING, call + + or_(TYPE_NUMBER, TYPE_STRING) + +The return value of the `or_` function will in this case be a +`TypeDisjunction` object that holds the two types. It means the +same as 'TYPE_NUMBER or TYPE_STRING' in English. The `or_` +function knows automatically from the type of its arguments +which type of object it must return. + +What if it is Impossible to Predict the Return Type of a Block? +--------------------------------------------------------------- + +In the case of the 'box' block, for example, it is impossible to +predict what type it will return at runtime because one cannot +foresee what will be inside the box at runtime. (E.g., the box +contents could depend on input from the keyboard or camera.) +This is where the special type TYPE_BOX comes in handy. It +allows you to postpone the search for a type converter until the +type of the box contents is known. As soon as this is the case, +the type system will automatically apply the appropriate type +converter. + +How to Add a New Type +--------------------- + +To add a new type to the type system, you need to instantiate a +new `Type` object and store it in a constant whose name starts +with `TYPE_`. You would do this in `tatype.py`: + + TYPE_MYTYPE = Type('TYPE_MYTYPE', 99) + +The number argument to the `Type` constructor can have an +arbitrary value, as long as it is different from the value of +every other `Type` object. + +You also need to tell the type system how to recognize runtime +objects that belong to your type. Add one or several new `elif` +clauses to the `get_type` function. E.g., if you are defining a +new type for dictionaries, you would add the clauses + + elif isinstance(x, dict): + return (TYPE_DICT, False) + elif isinstance(x, ast.Dict): + return (TYPE_DICT, True) + +The second item of the tuple that `get_type` returns indicates +whether `x` is an AST (Abstract Syntax Tree) or not. Only +instances of subclasses of `ast.AST` are ASTs. + +Optionally, you can add converters for the new type. You can do +so by extending the dictionary `TYPE_CONVERTERS` in `tatype.py`. +The format is quite simple: To add a converter from your type to +e.g., TYPE_FLOAT, add the entry: + + TYPE_CONVERTERS = { + # ... + TYPE_MYTYPE: { + # ... + TYPE_FLOAT: float + # ... + } + # ... + } + +Note that it is not obligatory to use the function `float` as +the converter to the type TYPE_FLOAT. In fact, you can use any +function or method. Please make sure that the converter accepts +arguments of the source type (here, TYPE_MYTYPE) and returns a +value of the target type (here, TYPE_FLOAT). The converter must +not throw any errors. diff --git a/icons/python-saveoff.svg b/icons/python-saveoff.svg new file mode 100644 index 0000000..1064219 --- /dev/null +++ b/icons/python-saveoff.svg @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + version="1.1" + width="55" + height="55" + viewBox="0 0 54.999998 55.000001" + id="Icon" + xml:space="preserve" + style="overflow:visible"><metadata + id="metadata26"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs24"><linearGradient + id="linearGradient3166-6"><stop + id="stop3168-5" + style="stop-color:#ffffff;stop-opacity:1" + offset="0" /><stop + id="stop3170-6" + style="stop-color:#ff0000;stop-opacity:1" + offset="1" /></linearGradient><linearGradient + x1="0" + y1="22" + x2="74" + y2="22" + id="linearGradient3172-9" + xlink:href="#linearGradient3166-6" + gradientUnits="userSpaceOnUse" /><linearGradient + id="linearGradient3166"><stop + id="stop3168" + style="stop-color:#ffffff;stop-opacity:1" + offset="0" /><stop + id="stop3170" + style="stop-color:#ffff00;stop-opacity:1" + offset="1" /></linearGradient><linearGradient + x1="0.94254935" + y1="-31.669659" + x2="104.37702" + y2="20.434471" + id="linearGradient3172" + xlink:href="#linearGradient3166" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" /></defs><g + transform="matrix(1.1181651,0,0,1.1181651,38.678832,-23.386068)" + id="g3348" + style="fill:none;stroke:#ffffff;stroke-opacity:1"><path + d="m -19.754174,46.744351 c 3.555069,0 8.83424,-1.56838 8.83424,-6.181226 0,-5.131219 -4.597011,-5.60538 -6.503378,-6.124857 -2.107038,-0.441556 -3.510542,-1.057744 -3.594847,-1.909356 -0.144269,-1.460061 0.687503,-2.028723 2.342736,-2.028723 0,0 3.937412,2.024856 7.282311,0.40895 0.942794,-0.454819 2.631273,-2.579702 2.631273,-4.04529 0,-1.466142 -5.450749,-3.160522 -7.104794,-3.160522 -1.655233,0 -3.062894,2.125988 -3.062894,2.125988 -3.309277,0 -6.619149,2.932283 -6.619149,5.864566 0,2.93173 3.166197,5.225166 6.950433,5.864565 1.759131,0.259187 3.230316,1.226851 2.896064,3.005231 -0.27132,1.444036 -1.778128,2.932283 -4.963917,2.932283 -2.524407,0 -7.896195,-0.121026 -8.75409,-2.254199 -0.551547,-1.373851 0.09974,-2.876467 0.927358,-2.876467 l -0.01603,-0.08842 c -0.843052,-0.08732 -3.293841,0.08842 -3.293841,3.020151 -5.94e-4,3.759026 5.428782,5.447327 12.048526,5.447327 z" + id="path2474" + style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path + d="m -11.393706,30.909692 c -1.557272,-0.158607 -3.924943,-1.105272 -4.43315,-2.775335" + id="path2476" + style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><circle + cx="35.805" + cy="10.96" + r="1.676" + transform="matrix(0.59369893,0,0,0.5526353,-36.672813,19.93767)" + id="circle2478" + style="fill:none;stroke:#ffffff;stroke-width:3.42034841;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><g + transform="translate(-1.2440512,-2.6168323)" + id="g3830"><g + transform="matrix(0.55205508,0,0,0.55205508,75.618464,18.235971)" + id="g4382"><g + transform="translate(-80.093659,12.220029)" + id="g4308" + style="fill:none;stroke:#ffffff;stroke-opacity:1"><g + id="g4310" + style="fill:none;stroke:#ffffff;stroke-opacity:1"><path + d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389" + id="path4312" + style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><g + transform="translate(-80.093659,12.220029)" + id="g4314" + style="fill:none;stroke:#ffffff;stroke-opacity:1"><g + id="g4316" + style="fill:none;stroke:#ffffff;stroke-opacity:1"><path + d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z" + id="path4318" + style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><path + d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543" + id="path4320" + style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path + d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543" + id="path4322" + style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path + d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544" + id="path4324" + style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line + id="line4326" + y2="23.725029" + y1="58.753029" + x2="-66.884659" + x1="-66.884659" + style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g><g + transform="matrix(1,0,0,-1,-30.386573,49.171266)" + id="g4770"><g + transform="translate(34.0803,-1006.42)" + id="g4772"><polyline + id="polyline4774" + points="51.562,15.306 41.17,16.188 42.053,5.794" + style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" + transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)" /><path + d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067" + id="path4776" + style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></g></svg>
\ No newline at end of file diff --git a/icons/python-saveon.svg b/icons/python-saveon.svg new file mode 100644 index 0000000..8871ae7 --- /dev/null +++ b/icons/python-saveon.svg @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + version="1.1" + width="55" + height="55" + viewBox="0 0 54.999998 55.000001" + id="Icon" + xml:space="preserve" + style="overflow:visible"><metadata + id="metadata26"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs24"><linearGradient + id="linearGradient3166-6"><stop + id="stop3168-5" + style="stop-color:#ffffff;stop-opacity:1" + offset="0" /><stop + id="stop3170-6" + style="stop-color:#ff0000;stop-opacity:1" + offset="1" /></linearGradient><linearGradient + x1="0" + y1="22" + x2="74" + y2="22" + id="linearGradient3172-9" + xlink:href="#linearGradient3166-6" + gradientUnits="userSpaceOnUse" /><linearGradient + id="linearGradient3166"><stop + id="stop3168" + style="stop-color:#ffffff;stop-opacity:1" + offset="0" /><stop + id="stop3170" + style="stop-color:#ffff00;stop-opacity:1" + offset="1" /></linearGradient><linearGradient + x1="0.94254935" + y1="-31.669659" + x2="104.37702" + y2="20.434471" + id="linearGradient3172" + xlink:href="#linearGradient3166" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(0.7083638,0,0,1.0012565,0.1338084,32.632067)" /></defs><g + transform="matrix(1.1181651,0,0,1.1181651,38.678832,-23.386068)" + id="g3348" + style="fill:none;stroke:#00ff00;stroke-opacity:1"><path + d="m -19.754174,46.744351 c 3.555069,0 8.83424,-1.56838 8.83424,-6.181226 0,-5.131219 -4.597011,-5.60538 -6.503378,-6.124857 -2.107038,-0.441556 -3.510542,-1.057744 -3.594847,-1.909356 -0.144269,-1.460061 0.687503,-2.028723 2.342736,-2.028723 0,0 3.937412,2.024856 7.282311,0.40895 0.942794,-0.454819 2.631273,-2.579702 2.631273,-4.04529 0,-1.466142 -5.450749,-3.160522 -7.104794,-3.160522 -1.655233,0 -3.062894,2.125988 -3.062894,2.125988 -3.309277,0 -6.619149,2.932283 -6.619149,5.864566 0,2.93173 3.166197,5.225166 6.950433,5.864565 1.759131,0.259187 3.230316,1.226851 2.896064,3.005231 -0.27132,1.444036 -1.778128,2.932283 -4.963917,2.932283 -2.524407,0 -7.896195,-0.121026 -8.75409,-2.254199 -0.551547,-1.373851 0.09974,-2.876467 0.927358,-2.876467 l -0.01603,-0.08842 c -0.843052,-0.08732 -3.293841,0.08842 -3.293841,3.020151 -5.94e-4,3.759026 5.428782,5.447327 12.048526,5.447327 z" + id="path2474" + style="fill:none;stroke:#00ff00;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><path + d="m -11.393706,30.909692 c -1.557272,-0.158607 -3.924943,-1.105272 -4.43315,-2.775335" + id="path2476" + style="fill:none;stroke:#00ff00;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /><circle + cx="35.805" + cy="10.96" + r="1.676" + transform="matrix(0.59369893,0,0,0.5526353,-36.672813,19.93767)" + id="circle2478" + style="fill:none;stroke:#00ff00;stroke-width:3.42034841;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g><g + transform="translate(-1.2440512,-2.6168323)" + id="g3830"><g + transform="matrix(0.55205508,0,0,0.55205508,75.618464,18.235971)" + id="g4382"><g + transform="translate(-80.093659,12.220029)" + id="g4308" + style="fill:none;stroke:#ffffff;stroke-opacity:1"><g + id="g4310" + style="fill:none;stroke:#ffffff;stroke-opacity:1"><path + d="m 6.736,49.002 h 24.52 c 2.225,0 3.439,-1.447 3.439,-3.441 v -27.28 c 0,-1.73 -1.732,-3.441 -3.439,-3.441 h -4.389" + id="path4312" + style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><g + transform="translate(-80.093659,12.220029)" + id="g4314" + style="fill:none;stroke:#ffffff;stroke-opacity:1"><g + id="g4316" + style="fill:none;stroke:#ffffff;stroke-opacity:1"><path + d="m 26.867,38.592 c 0,1.836 -1.345,3.201 -3.441,4.047 L 6.736,49.002 V 14.84 l 16.69,-8.599 c 2.228,-0.394 3.441,0.84 3.441,2.834 v 29.517 z" + id="path4318" + style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g></g><path + d="m -70.669659,54.827029 c 0,0 -1.351,-0.543 -2.702,-0.543 -1.351,0 -2.703,0.543 -2.703,0.543" + id="path4320" + style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path + d="m -70.669659,44.226029 c 0,0 -1.239,-0.543 -2.815,-0.543 -1.577,0 -2.59,0.543 -2.59,0.543" + id="path4322" + style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><path + d="m -70.669659,33.898029 c 0,0 -1.125,-0.544 -2.927,-0.544 -1.802,0 -2.478,0.544 -2.478,0.544" + id="path4324" + style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /><line + id="line4326" + y2="23.725029" + y1="58.753029" + x2="-66.884659" + x1="-66.884659" + style="fill:none;stroke:#ffffff;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1" /></g><g + transform="matrix(1,0,0,-1,-30.386573,49.171266)" + id="g4770"><g + transform="translate(34.0803,-1006.42)" + id="g4772"><polyline + id="polyline4774" + points="51.562,15.306 41.17,16.188 42.053,5.794" + style="fill:none;stroke:#ffffff;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round" + transform="matrix(-0.469241,0.469241,-0.469241,-0.469241,66.2906,1019.03)" /><path + d="m 39.363241,1033.1291 -0.05636,9.9115 -8.750608,0.067" + id="path4776" + style="fill:none;stroke:#ffffff;stroke-width:2.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /></g></g></g></svg>
\ No newline at end of file diff --git a/plugins/camera_sensor/camera_sensor.py b/plugins/camera_sensor/camera_sensor.py index 6509a88..67eb77d 100644 --- a/plugins/camera_sensor/camera_sensor.py +++ b/plugins/camera_sensor/camera_sensor.py @@ -174,8 +174,9 @@ is pushed to the stack'), def start(self): ''' Initialize the camera if there is an camera block in use ''' - if len(self._parent.block_list.get_similar_blocks('block', - ['camera', 'camera1', 'read_camera', 'luminance'])) > 0: + if (not self._parent.running_turtleart + or len(self._parent.block_list.get_similar_blocks('block', + ['camera', 'camera1', 'read_camera', 'luminance'])) > 0): if self._status and len(self.cameras) == 0: for device in self.devices: self.cameras.append(Camera(device)) diff --git a/plugins/turtle_blocks_extras/turtle_blocks_extras.py b/plugins/turtle_blocks_extras/turtle_blocks_extras.py index a0fa6c3..2e867eb 100644 --- a/plugins/turtle_blocks_extras/turtle_blocks_extras.py +++ b/plugins/turtle_blocks_extras/turtle_blocks_extras.py @@ -32,12 +32,15 @@ from TurtleArt.talogo import (primitive_dictionary, logoerror, media_blocks_dictionary) from TurtleArt.taconstants import (DEFAULT_SCALE, ICON_SIZE, CONSTANTS, MEDIA_SHAPES, SKIN_PATHS, BLOCKS_WITH_SKIN, - PYTHON_SKIN, PREFIX_DICTIONARY, VOICES, - MACROS, COLORDICT) + PYTHON_SKIN, MEDIA_BLOCK2TYPE, VOICES, + MACROS, Color) 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.tajail import myfunc_import +from TurtleArt.taprimitive import (ArgSlot, ConstantArg, Primitive) +from TurtleArt.tatype import (TYPE_BOOL, TYPE_BOX, TYPE_CHAR, TYPE_INT, + TYPE_FLOAT, TYPE_OBJECT, TYPE_STRING) def _num_type(x): @@ -99,6 +102,15 @@ class Turtle_blocks_extras(Plugin): special_name=_('while'), help_string=_('do-while-True operator that uses \ boolean operators from Numbers palette')) + self.tw.lc.def_prim('while', 2, + Primitive(self.tw.lc.prim_loop, + arg_descs=[ + ArgSlot(TYPE_OBJECT, + call_arg=False, + wrapper=Primitive(Primitive.controller_while, + arg_descs=[ArgSlot(TYPE_BOOL, call_arg=False)])), + ArgSlot(TYPE_OBJECT)]), + True) # internally expanded macro palette.add_block('until', @@ -109,15 +121,26 @@ boolean operators from Numbers palette')) special_name=_('until'), help_string=_('do-until-True operator that uses \ boolean operators from Numbers palette')) + self.tw.lc.def_prim('until', 2, + Primitive(self.tw.lc.prim_loop, + arg_descs=[ + ArgSlot(TYPE_OBJECT, + call_arg=False, + # TODO can we use controller_while in combination with not_? + wrapper=Primitive(Primitive.controller_until, + arg_descs=[ArgSlot(TYPE_BOOL, call_arg=False)])), + ArgSlot(TYPE_OBJECT)]), + True) - primitive_dictionary['clamp'] = self._prim_clamp palette.add_block('sandwichclamp', style='clamp-style-collapsible', label=' ', special_name=_('top'), prim_name='clamp', help_string=_('top of a collapsible stack')) - self.tw.lc.def_prim('clamp', 1, primitive_dictionary['clamp'], True) + self.tw.lc.def_prim('clamp', 1, + Primitive(self.tw.lc.prim_clamp, arg_descs=[ArgSlot(TYPE_OBJECT)]), + True) def _media_palette(self): palette = make_palette('media', @@ -131,7 +154,7 @@ boolean operators from Numbers palette')) default='None', special_name=_('journal'), help_string=_('Sugar Journal media object')) - PREFIX_DICTIONARY['journal'] = '#smedia_' + MEDIA_BLOCK2TYPE['journal'] = 'media' BLOCKS_WITH_SKIN.append('journal') MEDIA_SHAPES.append('journalsmall') MEDIA_SHAPES.append('journaloff') @@ -144,7 +167,7 @@ boolean operators from Numbers palette')) default='None', help_string=_('Sugar Journal audio object')) BLOCKS_WITH_SKIN.append('audio') - PREFIX_DICTIONARY['audio'] = '#saudio_' + MEDIA_BLOCK2TYPE['audio'] = 'audio' MEDIA_SHAPES.append('audiosmall') MEDIA_SHAPES.append('audiooff') MEDIA_SHAPES.append('audioon') @@ -156,7 +179,7 @@ boolean operators from Numbers palette')) default='None', help_string=_('Sugar Journal video object')) BLOCKS_WITH_SKIN.append('video') - PREFIX_DICTIONARY['video'] = '#svideo_' + MEDIA_BLOCK2TYPE['video'] = 'video' MEDIA_SHAPES.append('videosmall') MEDIA_SHAPES.append('videooff') MEDIA_SHAPES.append('videoon') @@ -168,7 +191,7 @@ boolean operators from Numbers palette')) default='None', help_string=_('Sugar Journal description field')) BLOCKS_WITH_SKIN.append('description') - PREFIX_DICTIONARY['description'] = '#sdescr_' + MEDIA_BLOCK2TYPE['description'] = 'descr' MEDIA_SHAPES.append('descriptionsmall') MEDIA_SHAPES.append('descriptionoff') MEDIA_SHAPES.append('descriptionon') @@ -180,7 +203,6 @@ boolean operators from Numbers palette')) special_name=_('text'), help_string=_('string value')) - primitive_dictionary['show'] = self._prim_show palette.add_block('show', style='basic-style-1arg', label=_('show'), @@ -190,8 +212,8 @@ boolean operators from Numbers palette')) help_string=_('draws text or show media from the \ Journal')) self.tw.lc.def_prim('show', 1, - lambda self, x: - primitive_dictionary['show'](x, True)) + Primitive(self.tw.lc.show, + arg_descs=[ArgSlot(TYPE_OBJECT), ConstantArg(True)])) palette.add_block('showaligned', hidden=True, @@ -204,8 +226,8 @@ Journal')) help_string=_('draws text or show media from the \ Journal')) self.tw.lc.def_prim('showaligned', 1, - lambda self, x: - primitive_dictionary['show'](x, False)) + Primitive(self.tw.lc.show, + arg_descs=[ArgSlot(TYPE_OBJECT), ConstantArg(False)])) primitive_dictionary['setscale'] = self._prim_setscale palette.add_block('setscale', @@ -419,7 +441,6 @@ to the stack')) self.tw.lc.def_prim('see', 0, lambda self: primitive_dictionary['see']()) - primitive_dictionary['time'] = self._prim_time palette.add_block('time', style='box-style', label=_('time'), @@ -428,7 +449,15 @@ to the stack')) help_string=_('elapsed time (in seconds) since \ program started')) self.tw.lc.def_prim('time', 0, - lambda self: primitive_dictionary['time']()) + Primitive(Primitive.identity, + return_type=TYPE_INT, + arg_descs=[ + ConstantArg(Primitive(int, arg_descs=[ + ConstantArg(Primitive(Primitive.minus, arg_descs=[ + ConstantArg(Primitive(time)), + ConstantArg(Primitive(self.tw.lc.get_start_time)) + ]))]))], + call_afterwards=self.after_time)) def _extras_palette(self): palette = make_palette('extras', @@ -446,11 +475,12 @@ program started')) help_string=_('pushes value onto FILO (first-in \ last-out heap)')) self.tw.lc.def_prim('push', 1, - lambda self, x: primitive_dictionary['push'](x)) + Primitive(self.tw.lc.heap.append, + arg_descs=[ArgSlot(TYPE_OBJECT)], + call_afterwards=self.after_push)) define_logo_function('tapush', 'to tapush :foo\nmake "taheap fput \ :foo :taheap\nend\nmake "taheap []\n') - primitive_dictionary['printheap'] = self._prim_printheap palette.add_block('printheap', style='basic-style-extended-vertical', label=_('show heap'), @@ -459,7 +489,9 @@ last-out heap)')) help_string=_('shows values in FILO (first-in \ last-out heap)')) self.tw.lc.def_prim('printheap', 0, - lambda self: primitive_dictionary['printheap']()) + Primitive(self.tw.print_, + arg_descs=[ConstantArg(Primitive(self.tw.lc.get_heap)), + ConstantArg(False)])) define_logo_function('taprintheap', 'to taprintheap \nprint :taheap\n\ end\n') @@ -472,7 +504,7 @@ end\n') help_string=_('emptys FILO (first-in-last-out \ heap)')) self.tw.lc.def_prim('clearheap', 0, - lambda self: primitive_dictionary['clearheap']()) + Primitive(self.tw.lc.reset_heap, call_afterwards=self.after_pop)) define_logo_function('taclearheap', 'to taclearheap\nmake "taheap []\n\ end\n') @@ -487,7 +519,8 @@ end\n') help_string=_('pops value off FILO (first-in \ last-out heap)')) self.tw.lc.def_prim('pop', 0, - lambda self: primitive_dictionary['pop']()) + Primitive(self.tw.lc.heap.pop, return_type=TYPE_BOX, + call_afterwards=self.after_pop)) define_logo_function('tapop', 'to tapop\nif emptyp :taheap [stop]\n\ make "tmp first :taheap\nmake "taheap butfirst :taheap\noutput :tmp\nend\n') @@ -500,7 +533,12 @@ make "tmp first :taheap\nmake "taheap butfirst :taheap\noutput :tmp\nend\n') value_block=True, help_string=_('returns True if heap is empty')) self.tw.lc.def_prim('isheapempty', 0, - lambda self: primitive_dictionary['isheapempty']()) + Primitive(int, return_type=TYPE_INT, + arg_descs=[ConstantArg( + Primitive(Primitive.not_, return_type=TYPE_BOOL, + arg_descs=[ConstantArg( + Primitive(self.tw.lc.get_heap, + return_type=TYPE_BOOL))]))])) primitive_dictionary['isheapempty2'] = self._prim_is_heap_empty_bool palette.add_block('isheapempty2', @@ -510,10 +548,12 @@ make "tmp first :taheap\nmake "taheap butfirst :taheap\noutput :tmp\nend\n') value_block=True, help_string=_('returns True if heap is empty')) self.tw.lc.def_prim('isheapempty2', 0, - lambda self: - primitive_dictionary['isheapempty2']()) + # Python automatically converts the heap to a boolean in contexts + # where a boolean is needed + Primitive(Primitive.not_, return_type=TYPE_BOOL, + arg_descs=[ConstantArg( + Primitive(self.tw.lc.get_heap, return_type=TYPE_BOOL))])) - primitive_dictionary['print'] = self._prim_print palette.add_block('comment', style='basic-style-1arg', label=_('comment'), @@ -522,8 +562,7 @@ make "tmp first :taheap\nmake "taheap butfirst :taheap\noutput :tmp\nend\n') string_or_number=True, help_string=_('places a comment in your code')) self.tw.lc.def_prim('comment', 1, - lambda self, x: - primitive_dictionary['print'](x, True)) + Primitive(Primitive.comment, arg_descs=[ArgSlot(TYPE_STRING)])) palette.add_block('print', style='basic-style-1arg', @@ -534,28 +573,29 @@ make "tmp first :taheap\nmake "taheap butfirst :taheap\noutput :tmp\nend\n') help_string=_('prints value in status block at \ bottom of the screen')) self.tw.lc.def_prim('print', 1, - lambda self, x: - primitive_dictionary['print'](x, False)) + Primitive(self.tw.print_, + arg_descs=[ArgSlot(TYPE_OBJECT), ConstantArg(False)])) - primitive_dictionary['chr'] = self._prim_chr palette.add_block('chr', style='number-style-1arg', label='chr', prim_name='chr', help_string=_('Python chr operator')) self.tw.lc.def_prim('chr', 1, - lambda self, x: primitive_dictionary['chr'](x)) + Primitive(chr, return_type=TYPE_CHAR, + arg_descs=[ArgSlot(TYPE_INT)])) - primitive_dictionary['int'] = self._prim_int palette.add_block('int', style='number-style-1arg', label='int', prim_name='int', help_string=_('Python int operator')) self.tw.lc.def_prim('int', 1, - lambda self, x: primitive_dictionary['int'](x)) + # leave over the actual work to the type system, and just demand + # that the argument be converted to an integer + Primitive(Primitive.identity, return_type=TYPE_INT, + arg_descs=[ArgSlot(TYPE_INT)])) - primitive_dictionary['myfunction'] = self._prim_myfunction palette.add_block('myfunc1arg', style='number-style-var-arg', label=[_('Python'), 'f(x)', 'x'], @@ -565,8 +605,8 @@ bottom of the screen')) help_string=_('a programmable block: used to add \ advanced single-variable math equations, e.g., sin(x)')) self.tw.lc.def_prim('myfunction', 2, - lambda self, f, x: - primitive_dictionary['myfunction'](f, [x])) + Primitive(self.tw.lc.prim_myfunction, return_type=TYPE_FLOAT, + arg_descs=[ArgSlot(TYPE_STRING), ArgSlot(TYPE_FLOAT)])) palette.add_block('myfunc2arg', hidden=True, @@ -579,8 +619,9 @@ advanced single-variable math equations, e.g., sin(x)')) help_string=_('a programmable block: used to add \ advanced multi-variable math equations, e.g., sqrt(x*x+y*y)')) self.tw.lc.def_prim('myfunction2', 3, - lambda self, f, x, y: - primitive_dictionary['myfunction'](f, [x, y])) + Primitive(self.tw.lc.prim_myfunction, return_type=TYPE_FLOAT, + arg_descs=[ArgSlot(TYPE_STRING), ArgSlot(TYPE_FLOAT), + ArgSlot(TYPE_FLOAT)])) palette.add_block('myfunc3arg', hidden=True, @@ -593,8 +634,9 @@ advanced multi-variable math equations, e.g., sqrt(x*x+y*y)')) help_string=_('a programmable block: used to add \ advanced multi-variable math equations, e.g., sin(x+y+z)')) self.tw.lc.def_prim('myfunction3', 4, - lambda self, f, x, y, z: - primitive_dictionary['myfunction'](f, [x, y, z])) + Primitive(self.tw.lc.prim_myfunction, return_type=TYPE_FLOAT, + arg_descs=[ArgSlot(TYPE_STRING), ArgSlot(TYPE_FLOAT), + ArgSlot(TYPE_FLOAT), ArgSlot(TYPE_FLOAT)])) primitive_dictionary['userdefined'] = self._prim_myblock palette.add_block('userdefined', @@ -798,23 +840,21 @@ module found in the Journal')) templates'), position=9) - primitive_dictionary['hideblocks'] = self._prim_hideblocks palette.add_block('hideblocks', style='basic-style-extended-vertical', label=_('hide blocks'), prim_name='hideblocks', help_string=_('declutters canvas by hiding blocks')) self.tw.lc.def_prim('hideblocks', 0, - lambda self: primitive_dictionary['hideblocks']()) + Primitive(self._prim_hideblocks, export_me=False)) - primitive_dictionary['showblocks'] = self._prim_showblocks palette.add_block('showblocks', style='basic-style-extended-vertical', label=_('show blocks'), prim_name='showblocks', help_string=_('restores hidden blocks')) self.tw.lc.def_prim('showblocks', 0, - lambda self: primitive_dictionary['showblocks']()) + Primitive(self._prim_showblocks, export_me=False)) palette.add_block('fullscreen', style='basic-style-extended-vertical', @@ -887,7 +927,9 @@ Journal objects')) prim_name='lpos', logo_command='lpos', help_string=_('xcor of left of screen')) - self.tw.lc.def_prim('lpos', 0, lambda self: CONSTANTS['leftpos']) + self.tw.lc.def_prim('lpos', 0, + Primitive(CONSTANTS.get, return_type=TYPE_INT, + arg_descs=[ConstantArg('leftpos')])) palette.add_block('bottompos', style='box-style', @@ -895,7 +937,9 @@ Journal objects')) prim_name='bpos', logo_command='bpos', help_string=_('ycor of bottom of screen')) - self.tw.lc.def_prim('bpos', 0, lambda self: CONSTANTS['bottompos']) + self.tw.lc.def_prim('bpos', 0, + Primitive(CONSTANTS.get, return_type=TYPE_INT, + arg_descs=[ConstantArg('bottompos')])) palette.add_block('width', style='box-style', @@ -903,7 +947,9 @@ Journal objects')) prim_name='hres', logo_command='width', help_string=_('the canvas width')) - self.tw.lc.def_prim('hres', 0, lambda self: CONSTANTS['width']) + self.tw.lc.def_prim('hres', 0, + Primitive(CONSTANTS.get, return_type=TYPE_INT, + arg_descs=[ConstantArg('width')])) palette.add_block('rightpos', style='box-style', @@ -911,7 +957,9 @@ Journal objects')) prim_name='rpos', logo_command='rpos', help_string=_('xcor of right of screen')) - self.tw.lc.def_prim('rpos', 0, lambda self: CONSTANTS['rightpos']) + self.tw.lc.def_prim('rpos', 0, + Primitive(CONSTANTS.get, return_type=TYPE_INT, + arg_descs=[ConstantArg('rightpos')])) palette.add_block('toppos', style='box-style', @@ -919,7 +967,9 @@ Journal objects')) prim_name='tpos', logo_command='tpos', help_string=_('ycor of top of screen')) - self.tw.lc.def_prim('tpos', 0, lambda self: CONSTANTS['toppos']) + self.tw.lc.def_prim('tpos', 0, + Primitive(CONSTANTS.get, return_type=TYPE_INT, + arg_descs=[ConstantArg('toppos')])) palette.add_block('height', style='box-style', @@ -927,7 +977,9 @@ Journal objects')) prim_name='vres', logo_command='height', help_string=_('the canvas height')) - self.tw.lc.def_prim('vres', 0, lambda self: CONSTANTS['height']) + self.tw.lc.def_prim('vres', 0, + Primitive(CONSTANTS.get, return_type=TYPE_INT, + arg_descs=[ConstantArg('height')])) palette.add_block('titlex', hidden=True, @@ -936,7 +988,9 @@ Journal objects')) label=_('title x'), logo_command='titlex', prim_name='titlex') - self.tw.lc.def_prim('titlex', 0, lambda self: CONSTANTS['titlex']) + self.tw.lc.def_prim('titlex', 0, + Primitive(CONSTANTS.get, return_type=TYPE_INT, + arg_descs=[ConstantArg('titlex')])) palette.add_block('titley', hidden=True, @@ -945,7 +999,9 @@ Journal objects')) label=_('title y'), logo_command='titley', prim_name='titley') - self.tw.lc.def_prim('titley', 0, lambda self: CONSTANTS['titley']) + self.tw.lc.def_prim('titley', 0, + Primitive(CONSTANTS.get, return_type=TYPE_INT, + arg_descs=[ConstantArg('titley')])) palette.add_block('leftx', hidden=True, @@ -954,7 +1010,9 @@ Journal objects')) label=_('left x'), prim_name='leftx', logo_command='leftx') - self.tw.lc.def_prim('leftx', 0, lambda self: CONSTANTS['leftx']) + self.tw.lc.def_prim('leftx', 0, + Primitive(CONSTANTS.get, return_type=TYPE_INT, + arg_descs=[ConstantArg('leftx')])) palette.add_block('topy', hidden=True, @@ -963,7 +1021,9 @@ Journal objects')) label=_('top y'), prim_name='topy', logo_command='topy') - self.tw.lc.def_prim('topy', 0, lambda self: CONSTANTS['topy']) + self.tw.lc.def_prim('topy', 0, + Primitive(CONSTANTS.get, return_type=TYPE_INT, + arg_descs=[ConstantArg('topy')])) palette.add_block('rightx', hidden=True, @@ -972,7 +1032,9 @@ Journal objects')) label=_('right x'), prim_name='rightx', logo_command='rightx') - self.tw.lc.def_prim('rightx', 0, lambda self: CONSTANTS['rightx']) + self.tw.lc.def_prim('rightx', 0, + Primitive(CONSTANTS.get, return_type=TYPE_INT, + arg_descs=[ConstantArg('rightx')])) palette.add_block('bottomy', hidden=True, @@ -981,7 +1043,9 @@ Journal objects')) label=_('bottom y'), prim_name='bottomy', logo_command='bottomy') - self.tw.lc.def_prim('bottomy', 0, lambda self: CONSTANTS['bottomy']) + self.tw.lc.def_prim('bottomy', 0, + Primitive(CONSTANTS.get, return_type=TYPE_INT, + arg_descs=[ConstantArg('bottomy')])) def _myblocks_palette(self): ''' User-defined macros are saved as a json-encoded file; @@ -1047,39 +1111,6 @@ Journal objects')) except: raise logoerror("#syntaxerror") - def _prim_myfunction(self, f, x): - """ Programmable block """ - for i, v in enumerate(x): - if type(v) == int: # Pass float values to Python block - x[i] = float(v) - try: - y = myfunc(f, x) - if str(y) == 'nan': - debug_output('Python function returned NAN', - self.tw.running_sugar) - self.tw.lc.stop_logo() - raise logoerror("#notanumber") - else: - return y - except ZeroDivisionError: - self.tw.lc.stop_logo() - raise logoerror("#zerodivide") - except ValueError, e: - self.tw.lc.stop_logo() - raise logoerror('#' + str(e)) - except SyntaxError, e: - self.tw.lc.stop_logo() - raise logoerror('#' + str(e)) - except NameError, e: - self.tw.lc.stop_logo() - raise logoerror('#' + str(e)) - except OverflowError: - self.tw.lc.stop_logo() - raise logoerror("#overflowerror") - except TypeError: - self.tw.lc.stop_logo() - raise logoerror("#notanumber") - def _prim_is_heap_empty(self): """ is FILO empty? """ if len(self.tw.lc.heap) == 0: @@ -1106,55 +1137,12 @@ Journal objects')) self.tw.lc.update_label_value('pop', self.tw.lc.heap[-2]) return self.tw.lc.heap.pop(-1) - def _prim_print(self, n, flag): - """ Print object n """ - if flag and (self.tw.hide or self.tw.step_time == 0): - return - if type(n) == list: - self.tw.showlabel('print', n) - elif type(n) == str or type(n) == unicode: - if n in COLORDICT: - if COLORDICT[n][0] is None: - self.tw.showlabel('print', '%s %d, %s %d' % - (_('shade'), COLORDICT[n][1], - _('gray'), COLORDICT[n][2])) - else: - self.tw.showlabel('print', '%s %d, %s %d, %s %d' % - (_('color'), COLORDICT[n][0], - _('shade'), COLORDICT[n][1], - _('gray'), COLORDICT[n][2])) - elif n[0:6] == 'media_' and \ - n[6:].lower not in media_blocks_dictionary: - try: - if self.tw.running_sugar: - from sugar.datastore import datastore - try: - dsobject = datastore.get(n[6:]) - except: - debug_output("Couldn't open %s" % (n[6:]), - self.tw.running_sugar) - self.tw.showlabel('print', dsobject.metadata['title']) - dsobject.destroy() - else: - self.tw.showlabel('print', n[6:]) - except IOError: - self.tw.showlabel('print', n) + def after_pop(self): + if self.tw.lc.update_values: + if not self.tw.lc.heap: + self.tw.lc.update_label_value('pop') else: - self.tw.showlabel('print', n) - elif type(n) == int: - self.tw.showlabel('print', n) - else: - self.tw.showlabel( - 'print', - str(round_int(n)).replace('.', self.tw.decimal_point)) - - def _prim_printheap(self): - """ Display contents of heap """ - heap_as_string = str(self.tw.lc.heap) - if len(heap_as_string) > 80: - self.tw.showlabel('print', str(self.tw.lc.heap)[0:79] + '…') - else: - self.tw.showlabel('print', str(self.tw.lc.heap)) + self.tw.lc.update_label_value('pop', self.tw.lc.heap[-1]) def _prim_push(self, val): """ Push value onto FILO """ @@ -1162,6 +1150,10 @@ Journal objects')) if self.tw.lc.update_values: self.tw.lc.update_label_value('pop', val) + def after_push(self, val): + if self.tw.lc.update_values: + self.tw.lc.update_label_value('pop', val) + def _prim_readpixel(self): """ Read r, g, b, a from the canvas and push b, g, r to the stack """ r, g, b, a = self.tw.turtles.get_active_turtle().get_pixel() @@ -1359,78 +1351,6 @@ Journal objects')) if self.tw.lc.update_values: self.tw.lc.update_label_value('scale', scale) - def _prim_show(self, string, center=False): - """ Show is the general-purpose media-rendering block. """ - if type(string) == str or type(string) == unicode: - if string in ['media_', 'descr_', 'audio_', 'video_', - 'media_None', 'descr_None', 'audio_None', - 'video_None']: - pass - elif string[0:6] in ['media_', 'descr_', 'audio_', 'video_']: - self.tw.lc.filepath = None - self.tw.lc.pixbuf = None # Camera writes directly to pixbuf - self.tw.lc.dsobject = None - if string[6:].lower() in media_blocks_dictionary: - media_blocks_dictionary[string[6:].lower()]() - elif os.path.exists(string[6:]): # is it a path? - self.tw.lc.filepath = string[6:] - elif self.tw.running_sugar: # is it a datastore object? - from sugar.datastore import datastore - try: - self.tw.lc.dsobject = datastore.get(string[6:]) - except: - debug_output("Couldn't find dsobject %s" % - (string[6:]), self.tw.running_sugar) - if self.tw.lc.dsobject is not None: - self.tw.lc.filepath = self.tw.lc.dsobject.file_path - if self.tw.lc.pixbuf is not None: - self.tw.lc.insert_image(center=center, pixbuf=True) - elif self.tw.lc.filepath is None: - if self.tw.lc.dsobject is not None: - self.tw.showlabel( - 'nojournal', - self.tw.lc.dsobject.metadata['title']) - else: - self.tw.showlabel('nojournal', string[6:]) - debug_output("Couldn't open %s" % (string[6:]), - self.tw.running_sugar) - elif string[0:6] == 'media_': - self.tw.lc.insert_image(center=center) - elif string[0:6] == 'descr_': - mimetype = None - if self.tw.lc.dsobject is not None and \ - 'mime_type' in self.tw.lc.dsobject.metadata: - mimetype = self.tw.lc.dsobject.metadata['mime_type'] - description = None - if self.tw.lc.dsobject is not None and \ - 'description' in self.tw.lc.dsobject.metadata: - description = self.tw.lc.dsobject.metadata[ - 'description'] - self.tw.lc.insert_desc(mimetype, description) - elif string[0:6] == 'audio_': - self.tw.lc.play_sound() - elif string[0:6] == 'video_': - self.tw.lc.play_video() - if self.tw.lc.dsobject is not None: - self.tw.lc.dsobject.destroy() - else: # assume it is text to display - x, y = self.tw.lc.x2tx(), self.tw.lc.y2ty() - if center: - y -= self.tw.canvas.textsize - self.tw.turtles.get_active_turtle().draw_text(string, x, y, - int(self.tw.canvas.textsize * - self.tw.lc.scale / 100.), - self.tw.canvas.width - x) - elif type(string) == float or type(string) == int: - string = round_int(string) - x, y = self.tw.lc.x2tx(), self.tw.lc.y2ty() - if center: - y -= self.tw.canvas.textsize - self.tw.turtles.get_active_turtle().draw_text(string, x, y, - int(self.tw.canvas.textsize * - self.tw.lc.scale / 100.), - self.tw.canvas.width - x) - def _prim_showlist(self, sarray): """ Display list of media objects """ x = (self.tw.turtles.get_active_turtle().get_xy()[0] / @@ -1450,6 +1370,12 @@ Journal objects')) self.tw.lc.update_label_value('time', elapsed_time) return elapsed_time + def after_time(self, elapsed_time): + """ Update the label of the 'time' block after computing the new + value. """ + if self.tw.lc.update_values: + self.tw.lc.update_label_value('time', elapsed_time) + def _prim_hideblocks(self): """ hide blocks and show showblocks button """ self.tw.hideblocks() 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..45c7ba4 --- /dev/null +++ b/pyexported/window_setup.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python + +import cairo +import pygtk +pygtk.require('2.0') +import gtk +import gobject + +import os +from sys import argv + +from TurtleArt.tablock import Media +from TurtleArt.taconstants import CONSTANTS +from TurtleArt.tatype import * +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(str(gui.name)) + # 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/samples/game-set.ta b/samples/game-set.ta index f917dba..98d35b6 100644 --- a/samples/game-set.ta +++ b/samples/game-set.ta @@ -1,298 +1,302 @@ [[0, "hat", 599, 213, [null, 1, 258]], -[1, ["string", "shape1"], 657, 221, [0, null]], +[1, ["string", "shape1"], 657, 225, [0, null]], [2, "hat", 595, 367, [null, 3, 257]], -[3, ["string", "shape2"], 653, 375, [2, null]], -[4, "forward", 664, 525, [10, 5, 6]], -[5, ["number", 70], 735, 525, [4, null]], -[6, "back", 664, 567, [4, 7, 24]], -[7, ["number", 70], 722, 567, [6, null]], -[8, "seth", 599, 339, [32, 9, 19]], -[9, ["number", 45], 698, 339, [8, null]], -[10, "repeat", 599, 465, [28, 11, 4, 12]], -[11, ["number", 4], 650, 465, [10, null]], -[12, ["vspace", 40], 599, 543, [10, 21]], -[13, "repeat", 599, 749, [23, 14, 15, 126]], -[14, ["number", 4], 650, 749, [13, null]], -[15, "forward", 664, 809, [13, 16, 17]], -[16, ["number", 67], 735, 809, [15, null]], -[17, "back", 664, 851, [15, 18, 26]], -[18, ["number", 67], 722, 851, [17, null]], -[19, "setpensize", 599, 381, [8, 20, 28]], -[20, ["number", 40], 701, 381, [19, null]], -[21, "setpensize", 599, 665, [12, 22, 23]], -[22, ["number", 20], 701, 665, [21, null]], -[23, "setshade", 599, 707, [21, 30, 13]], -[24, "right", 664, 609, [6, 25, null]], -[25, ["number", 90], 722, 609, [24, null]], -[26, "right", 664, 893, [17, 27, null]], -[27, ["number", 90], 722, 893, [26, null]], -[28, "setshade", 599, 423, [19, 29, 10]], -[29, ["number", 50], 684, 423, [28, null]], -[30, "box", 684, 707, [23, 31, null]], -[31, ["string", "shade"], 739, 707, [30, null]], -[32, "setcolor", 599, 297, [258, 33, 8]], -[33, "box", 676, 297, [32, 34, null]], -[34, ["string", "color"], 731, 297, [33, null]], -[35, ["storein", 0], 321, 433, [263, 36, 37, 50]], -[36, ["string", "color"], 389, 433, [35, null]], -[37, ["number", 0], 389, 475, [35, null]], -[38, ["storein", 0], 318, 742, [265, 39, 40, 41]], -[39, ["string", "shade"], 386, 742, [38, null]], -[40, ["number", 0], 386, 784, [38, null]], -[41, "repeat", 318, 826, [38, 42, 187, 75]], -[42, ["number", 3], 369, 826, [41, null]], -[43, "box", 505, 970, [45, 44, null]], -[44, ["string", "shade"], 560, 970, [43, null]], -[45, ["plus2", 0], 451, 970, [47, 43, 46]], -[46, ["number", 50], 505, 1012, [45, null]], -[47, ["storein", 0], 383, 928, [187, 48, 45, null]], -[48, ["string", "shade"], 451, 928, [47, null]], -[49, "stack", 109, 1035, [222, 64, 151]], -[50, "repeat", 321, 517, [35, 51, 76, 74]], -[51, ["number", 3], 372, 517, [50, null]], -[52, ["string", "shape"], 221, 1035, [64, null]], -[53, ["storein", 0], 386, 619, [76, 54, 56, null]], -[54, ["string", "color"], 454, 619, [53, null]], -[55, ["number", 35], 508, 703, [56, null]], -[56, ["plus2", 0], 454, 661, [53, 57, 55]], -[57, "box", 508, 661, [56, 58, null]], -[58, ["string", "color"], 563, 661, [57, null]], -[59, "box", 221, 1077, [64, 60, null]], -[60, ["string", "shape"], 276, 1077, [59, null]], -[61, ["storein", 0], 315, 287, [262, 62, 63, 65]], -[62, ["string", "shape"], 383, 287, [61, null]], -[63, ["number", 1], 383, 329, [61, null]], -[64, ["plus2", 0], 167, 1035, [49, 52, 59]], -[65, "repeat", 315, 371, [61, 66, 82, 73]], -[66, ["number", 3], 366, 371, [65, null]], -[67, ["storein", 0], 380, 473, [82, 68, 72, null]], -[68, ["string", "shape"], 448, 473, [67, null]], -[69, ["number", 1], 502, 557, [72, null]], -[70, "box", 502, 515, [72, 71, null]], -[71, ["string", "shape"], 557, 515, [70, null]], -[72, ["plus2", 0], 448, 515, [67, 70, 69]], -[73, ["sandwichcollapsed", 1], 315, 287, [65, null]], -[74, ["sandwichcollapsed", 1], 321, 433, [50, null]], -[75, ["sandwichcollapsed", 1], 318, 742, [41, null]], -[76, "stack", 386, 577, [50, 77, 53]], -[77, ["string", "numberloop"], 444, 577, [76, null]], +[3, ["string", "shape2"], 653, 379, [2, null]], +[4, "forward", 600, 487, [10, 5, 6]], +[5, ["number", 70], 672, 487, [4, null]], +[6, "back", 600, 529, [4, 7, 24]], +[7, ["number", 70], 658, 529, [6, null]], +[8, "seth", 582, 319, [32, 9, 19]], +[9, ["number", 45], 684, 319, [8, null]], +[10, ["repeat", 42], 582, 445, [28, 11, 4, 12]], +[11, ["number", 4], 644, 445, [10, null]], +[12, ["vspace", 40], 582, 631, [10, 21]], +[13, ["repeat", 42], 582, 837, [23, 14, 15, 126]], +[14, ["number", 4], 644, 837, [13, null]], +[15, "forward", 600, 879, [13, 16, 17]], +[16, ["number", 67], 672, 879, [15, null]], +[17, "back", 600, 921, [15, 18, 26]], +[18, ["number", 67], 658, 921, [17, null]], +[19, "setpensize", 582, 361, [8, 20, 28]], +[20, ["number", 40], 685, 361, [19, null]], +[21, "setpensize", 582, 753, [12, 22, 23]], +[22, ["number", 20], 685, 753, [21, null]], +[23, "setshade", 582, 795, [21, 30, 13]], +[24, "right", 600, 571, [6, 25, null]], +[25, ["number", 90], 658, 571, [24, null]], +[26, "right", 600, 963, [17, 27, null]], +[27, ["number", 90], 658, 963, [26, null]], +[28, "setshade", 582, 403, [19, 29, 10]], +[29, ["number", 50], 668, 403, [28, null]], +[30, "box", 668, 795, [23, 31, null]], +[31, ["string", "shade"], 722, 795, [30, null]], +[32, "setcolor", 582, 277, [258, 33, 8]], +[33, "box", 660, 277, [32, 34, null]], +[34, ["string", "color"], 714, 277, [33, null]], +[35, ["storein", 0], 304, 413, [263, 36, 37, 50]], +[36, ["string", "color"], 373, 413, [35, null]], +[37, ["number", 0], 373, 455, [35, null]], +[38, ["storein", 0], 301, 722, [265, 39, 40, 41]], +[39, ["string", "shade"], 370, 722, [38, null]], +[40, ["number", 0], 370, 764, [38, null]], +[41, ["repeat", 42], 301, 806, [38, 42, 187, 75]], +[42, ["number", 3], 363, 806, [41, null]], +[43, "box", 442, 932, [45, 44, null]], +[44, ["string", "shade"], 496, 932, [43, null]], +[45, ["plus2", 0], 388, 932, [47, 43, 46]], +[46, ["number", 50], 442, 974, [45, null]], +[47, ["storein", 0], 319, 890, [187, 48, 45, null]], +[48, ["string", "shade"], 388, 890, [47, null]], +[49, "stack", 72, 1700, [222, 64, 151]], +[50, ["repeat", 42], 304, 497, [35, 51, 76, 74]], +[51, ["number", 3], 366, 497, [50, null]], +[52, ["string", "shape"], 184, 1700, [64, null]], +[53, ["storein", 0], 322, 581, [76, 54, 56, null]], +[54, ["string", "color"], 391, 581, [53, null]], +[55, ["number", 35], 445, 665, [56, null]], +[56, ["plus2", 0], 391, 623, [53, 57, 55]], +[57, "box", 445, 623, [56, 58, null]], +[58, ["string", "color"], 499, 623, [57, null]], +[59, "box", 238, 1742, [298, 60, null]], +[60, ["string", "shape"], 292, 1742, [59, null]], +[61, ["storein", 0], 333, 291, [262, 62, 63, 65]], +[62, ["string", "shape"], 402, 291, [61, null]], +[63, ["number", 1], 402, 333, [61, null]], +[64, ["plus2", 0], 130, 1700, [49, 52, 298]], +[65, ["repeat", 42], 333, 375, [61, 66, 82, 73]], +[66, ["number", 3], 395, 375, [65, null]], +[67, ["storein", 0], 351, 459, [82, 68, 72, null]], +[68, ["string", "shape"], 420, 459, [67, null]], +[69, ["number", 1], 474, 543, [72, null]], +[70, "box", 474, 501, [72, 71, null]], +[71, ["string", "shape"], 528, 501, [70, null]], +[72, ["plus2", 0], 420, 501, [67, 70, 69]], +[73, ["vspace", 1], 333, 561, [65, null]], +[74, ["vspace", 1], 304, 683, [50, null]], +[75, ["vspace", 1], 301, 992, [41, null]], +[76, "stack", 322, 539, [50, 77, 53]], +[77, ["string", "numberloop"], 380, 539, [76, null]], [78, "hat", 318, 658, [null, 79, 265]], -[79, ["string", "shadeloop"], 376, 666, [78, null]], +[79, ["string", "shadeloop"], 376, 670, [78, null]], [80, "hat", 321, 349, [null, 81, 263]], -[81, ["string", "colorloop"], 379, 357, [80, null]], -[82, "stack", 380, 431, [65, 83, 67]], -[83, ["string", "colorloop"], 438, 431, [82, null]], +[81, ["string", "colorloop"], 379, 361, [80, null]], +[82, "stack", 351, 417, [65, 83, 67]], +[83, ["string", "colorloop"], 409, 417, [82, null]], [84, "hat", 315, 203, [null, 85, 262]], -[85, ["string", "shapeloop"], 373, 211, [84, null]], -[86, "setcolor", 595, 451, [257, 87, 89]], -[87, "box", 672, 451, [86, 88, null]], -[88, ["string", "color"], 727, 451, [87, null]], -[89, "seth", 595, 493, [86, 90, 91]], -[90, ["number", 0], 694, 493, [89, null]], -[91, "setpensize", 595, 535, [89, 92, 93]], -[92, ["number", 150], 697, 535, [91, null]], -[93, "setshade", 595, 577, [91, 94, 95]], -[94, ["number", 50], 680, 577, [93, null]], -[95, "forward", 595, 619, [93, 96, 97]], -[96, ["number", 1], 666, 619, [95, null]], -[97, "back", 595, 661, [95, 98, 99]], -[98, ["number", 1], 653, 661, [97, null]], -[99, "setpensize", 595, 703, [97, 100, 101]], -[100, ["number", 130], 697, 703, [99, null]], -[101, "setshade", 595, 745, [99, 102, 106]], -[102, "box", 680, 745, [101, 103, null]], -[103, ["string", "shade"], 735, 745, [102, null]], -[104, "repeat", 319, 666, [134, 105, 139, 153]], -[105, ["number", 3], 370, 666, [104, null]], -[106, "forward", 595, 787, [101, 107, 108]], -[107, ["number", 1], 666, 787, [106, null]], -[108, "back", 595, 829, [106, 109, 110]], -[109, ["number", 1], 653, 829, [108, null]], -[110, "setpensize", 595, 871, [108, 111, 112]], -[111, ["number", 90], 697, 871, [110, null]], -[112, "setshade", 595, 913, [110, 113, 114]], -[113, ["number", 50], 680, 913, [112, null]], -[114, "forward", 595, 955, [112, 115, 116]], -[115, ["number", 1], 666, 955, [114, null]], -[116, "back", 595, 997, [114, 117, 118]], -[117, ["number", 1], 653, 997, [116, null]], -[118, "setpensize", 595, 1039, [116, 119, 120]], -[119, ["number", 70], 697, 1039, [118, null]], -[120, "setshade", 595, 1081, [118, 121, 122]], -[121, ["number", 100], 680, 1081, [120, null]], -[122, "forward", 595, 1123, [120, 123, 124]], -[123, ["number", 1], 666, 1123, [122, null]], -[124, "back", 595, 1165, [122, 125, 127]], -[125, ["number", 1], 653, 1165, [124, null]], -[126, ["sandwichcollapsed", 1], 599, 297, [13, null]], -[127, ["sandwichcollapsed", 1], 595, 451, [124, null]], -[128, ["fillscreen", 0], 38, 624, [261, 130, 129, 199]], -[129, ["number", "100"], 120, 666, [128, null]], -[130, "box", 120, 624, [128, 131, null]], -[131, ["string", "color"], 175, 624, [130, null]], +[85, ["string", "shapeloop"], 373, 215, [84, null]], +[86, "setcolor", 578, 431, [257, 87, 89]], +[87, "box", 656, 431, [86, 88, null]], +[88, ["string", "color"], 710, 431, [87, null]], +[89, "seth", 578, 473, [86, 90, 91]], +[90, ["number", 0], 680, 473, [89, null]], +[91, "setpensize", 578, 515, [89, 92, 93]], +[92, ["number", 150], 681, 515, [91, null]], +[93, "setshade", 578, 557, [91, 94, 95]], +[94, ["number", 50], 664, 557, [93, null]], +[95, "forward", 578, 599, [93, 96, 97]], +[96, ["number", 1], 650, 599, [95, null]], +[97, "back", 578, 641, [95, 98, 99]], +[98, ["number", 1], 636, 641, [97, null]], +[99, "setpensize", 578, 683, [97, 100, 101]], +[100, ["number", 130], 681, 683, [99, null]], +[101, "setshade", 578, 725, [99, 102, 106]], +[102, "box", 664, 725, [101, 103, null]], +[103, ["string", "shade"], 718, 725, [102, null]], +[104, ["repeat", 42], 302, 646, [134, 105, 139, 153]], +[105, ["number", 3], 364, 646, [104, null]], +[106, "forward", 578, 767, [101, 107, 108]], +[107, ["number", 1], 650, 767, [106, null]], +[108, "back", 578, 809, [106, 109, 110]], +[109, ["number", 1], 636, 809, [108, null]], +[110, "setpensize", 578, 851, [108, 111, 112]], +[111, ["number", 90], 681, 851, [110, null]], +[112, "setshade", 578, 893, [110, 113, 114]], +[113, ["number", 50], 664, 893, [112, null]], +[114, "forward", 578, 935, [112, 115, 116]], +[115, ["number", 1], 650, 935, [114, null]], +[116, "back", 578, 977, [114, 117, 118]], +[117, ["number", 1], 636, 977, [116, null]], +[118, "setpensize", 578, 1019, [116, 119, 120]], +[119, ["number", 70], 681, 1019, [118, null]], +[120, "setshade", 578, 1061, [118, 121, 122]], +[121, ["number", 100], 664, 1061, [120, null]], +[122, "forward", 578, 1103, [120, 123, 124]], +[123, ["number", 1], 650, 1103, [122, null]], +[124, "back", 578, 1145, [122, 125, 127]], +[125, ["number", 1], 636, 1145, [124, null]], +[126, ["vspace", 1], 582, 1023, [13, null]], +[127, ["vspace", 1], 578, 1187, [124, null]], +[128, ["fillscreen", 0], 21, 604, [261, 130, 129, 199]], +[129, ["number", 100], 107, 646, [128, null]], +[130, "box", 107, 604, [128, 131, null]], +[131, ["string", "color"], 161, 604, [130, null]], [132, "hat", 319, 498, [null, 133, 264]], -[133, ["string", "numberloop"], 377, 506, [132, null]], -[134, ["storein", 0], 319, 582, [264, 135, 136, 104]], -[135, ["string", "number"], 387, 582, [134, null]], -[136, ["number", 1], 387, 624, [134, null]], +[133, ["string", "numberloop"], 377, 510, [132, null]], +[134, ["storein", 0], 302, 562, [264, 135, 136, 104]], +[135, ["string", "number"], 371, 562, [134, null]], +[136, ["number", 1], 371, 604, [134, null]], [137, "hat", 591, 524, [null, 138, 256]], -[138, ["string", "shape3"], 649, 532, [137, null]], -[139, "stack", 384, 726, [104, 140, 179]], -[140, ["string", "shadeloop"], 442, 726, [139, null]], -[141, "repeat", 44, 807, [238, 142, 220, 228]], -[142, "box", 95, 807, [141, 143, null]], -[143, ["string", "number"], 150, 807, [142, null]], -[144, ["setxy2", 0], 444, 755, [249, 145, 146, 250]], -[145, ["number", -200], 502, 755, [144, null]], -[146, ["number", 0], 502, 797, [144, null]], -[147, "seth", 109, 1203, [224, 148, 149]], -[148, ["number", 90], 208, 1203, [147, null]], -[149, "forward", 109, 1245, [147, 150, 152]], -[150, ["number", 200], 180, 1245, [149, null]], -[151, "penup", 109, 1077, [49, 224]], -[152, "pendown", 109, 1287, [149, null]], -[153, ["sandwichcollapsed", 1], 319, 582, [104, null]], -[154, "wait", 44, 1327, [228, 155, 189]], -[155, ["number", 0.20000000000000001], 102, 1327, [154, null]], -[156, "setcolor", 591, 608, [256, 157, 171]], -[157, "box", 668, 608, [156, 158, null]], -[158, ["string", "color"], 723, 608, [157, null]], -[159, "setpensize", 591, 818, [169, 160, 161]], -[160, ["number", 40], 693, 818, [159, null]], -[161, "setshade", 591, 860, [159, 162, 282]], -[162, ["number", 50], 676, 860, [161, null]], -[163, "setpensize", 591, 1238, [296, 164, 165]], -[164, ["number", 20], 693, 1238, [163, null]], -[165, "setshade", 591, 1280, [163, 166, 266]], -[166, "box", 676, 1280, [165, 167, null]], -[167, ["string", "shade"], 731, 1280, [166, null]], -[168, ["sandwichcollapsed", 1], 591, 608, [280, null]], -[169, "pendown", 591, 776, [173, 159]], -[170, "penup", 591, 692, [171, 173]], -[171, "seth", 591, 650, [156, 172, 170]], -[172, ["number", 270], 690, 650, [171, null]], -[173, "forward", 591, 734, [170, 174, 169]], -[174, ["number", 50], 662, 734, [173, null]], +[138, ["string", "shape3"], 649, 536, [137, null]], +[139, "stack", 320, 688, [104, 140, 179]], +[140, ["string", "shadeloop"], 378, 688, [139, null]], +[141, ["repeat", 210], 54, 1490, [238, 142, 220, 228]], +[142, "box", 116, 1490, [141, 143, null]], +[143, ["string", "number"], 170, 1490, [142, null]], +[144, ["setxy2", 0], 226, 1286, [249, 145, 146, 250]], +[145, ["number", -200], 284, 1286, [144, null]], +[146, ["number", 0], 284, 1328, [144, null]], +[147, "seth", 72, 1868, [224, 148, 149]], +[148, ["number", 90], 174, 1868, [147, null]], +[149, "forward", 72, 1910, [147, 150, 152]], +[150, ["number", 200], 144, 1910, [149, null]], +[151, "penup", 72, 1742, [49, 224]], +[152, "pendown", 72, 1952, [149, null]], +[153, ["vspace", 1], 302, 832, [104, null]], +[154, "wait", 54, 2454, [228, 155, 189]], +[155, ["number", 0.2], 112, 2454, [154, null]], +[156, "setcolor", 574, 588, [256, 157, 171]], +[157, "box", 652, 588, [156, 158, null]], +[158, ["string", "color"], 706, 588, [157, null]], +[159, "setpensize", 574, 798, [169, 160, 161]], +[160, ["number", 40], 677, 798, [159, null]], +[161, "setshade", 574, 840, [159, 162, 282]], +[162, ["number", 50], 660, 840, [161, null]], +[163, "setpensize", 574, 1218, [296, 164, 165]], +[164, ["number", 20], 677, 1218, [163, null]], +[165, "setshade", 574, 1260, [163, 166, 266]], +[166, "box", 660, 1260, [165, 167, null]], +[167, ["string", "shade"], 714, 1260, [166, null]], +[168, ["vspace", 1], 574, 1638, [280, null]], +[169, "pendown", 574, 756, [173, 159]], +[170, "penup", 574, 672, [171, 173]], +[171, "seth", 574, 630, [156, 172, 170]], +[172, ["number", 270], 676, 630, [171, null]], +[173, "forward", 574, 714, [170, 174, 169]], +[174, ["number", 50], 646, 714, [173, null]], [175, ["start", 2.0], 34, 208, [null, 178]], -[176, "stack", 34, 292, [178, 177, 246]], -[177, ["string", "shapeloop"], 92, 292, [176, null]], -[178, "hideblocks", 34, 250, [175, 176]], -[179, ["storein", 0], 384, 768, [139, 180, 182, null]], -[180, ["string", "number"], 452, 768, [179, null]], -[181, ["number", 1], 506, 852, [182, null]], -[182, ["plus2", 0], 452, 810, [179, 183, 181]], -[183, "box", 506, 810, [182, 184, null]], -[184, ["string", "number"], 561, 810, [183, null]], -[185, "hat", 44, 391, [null, 186, 259]], -[186, ["string", "action"], 102, 399, [185, null]], -[187, "stack", 383, 886, [41, 188, 47]], -[188, ["string", "action"], 441, 886, [187, null]], -[189, ["sandwichcollapsed", 1], 44, 475, [154, null]], -[190, "forward", 103, 1062, [204, 191, 201]], -[191, ["number", "340"], 174, 1062, [190, null]], -[192, ["setxy2", 0], 38, 876, [247, 193, 194, 248]], -[193, ["number", -290], 96, 876, [192, null]], -[194, ["number", "-170"], 96, 918, [192, null]], -[195, "seth", 38, 792, [197, 196, 247]], -[196, ["number", 0], 137, 792, [195, null]], -[197, "setpensize", 38, 750, [199, 198, 195]], -[198, ["number", 5], 140, 750, [197, null]], -[199, "setshade", 38, 708, [128, 200, 197]], -[200, ["number", 25], 123, 708, [199, null]], -[201, ["arc", 0], 103, 1104, [190, 202, 203, 206]], -[202, ["number", 90], 161, 1104, [201, null]], -[203, ["number", 10], 161, 1146, [201, null]], -[204, "repeat", 38, 1002, [248, 205, 190, 211]], -[205, ["number", 2], 89, 1002, [204, null]], -[206, "forward", 103, 1188, [201, 207, 208]], -[207, ["number", "580"], 174, 1188, [206, null]], -[208, ["arc", 0], 103, 1230, [206, 209, 210, null]], -[209, ["number", 90], 161, 1230, [208, null]], -[210, ["number", 10], 161, 1272, [208, null]], -[211, ["sandwichcollapsed", 1], 38, 624, [204, null]], +[176, "stack", 34, 296, [178, 177, 246]], +[177, ["string", "shapeloop"], 92, 296, [176, null]], +[178, "hideblocks", 34, 254, [175, 176]], +[179, ["storein", 0], 320, 730, [139, 180, 182, null]], +[180, ["string", "number"], 389, 730, [179, null]], +[181, ["number", 1], 443, 814, [182, null]], +[182, ["plus2", 0], 389, 772, [179, 183, 181]], +[183, "box", 443, 772, [182, 184, null]], +[184, ["string", "number"], 497, 772, [183, null]], +[185, "hat", 36, 428, [null, 186, 259]], +[186, ["string", "action"], 94, 440, [185, null]], +[187, "stack", 319, 848, [41, 188, 47]], +[188, ["string", "action"], 377, 848, [187, null]], +[189, ["vspace", 1], 54, 2496, [154, null]], +[190, "forward", 39, 1024, [204, 191, 201]], +[191, ["number", 340], 111, 1024, [190, null]], +[192, ["setxy2", 0], 21, 856, [247, 193, 194, 248]], +[193, ["number", -290], 79, 856, [192, null]], +[194, ["number", -170], 79, 898, [192, null]], +[195, "seth", 21, 772, [197, 196, 247]], +[196, ["number", 0], 123, 772, [195, null]], +[197, "setpensize", 21, 730, [199, 198, 195]], +[198, ["number", 5], 124, 730, [197, null]], +[199, "setshade", 21, 688, [128, 200, 197]], +[200, ["number", 25], 107, 688, [199, null]], +[201, ["arc", 0], 39, 1066, [190, 202, 203, 206]], +[202, ["number", 90], 97, 1066, [201, null]], +[203, ["number", 10], 97, 1108, [201, null]], +[204, ["repeat", 105], 21, 982, [248, 205, 190, 211]], +[205, ["number", 2], 83, 982, [204, null]], +[206, "forward", 39, 1150, [201, 207, 208]], +[207, ["number", 580], 111, 1150, [206, null]], +[208, ["arc", 0], 39, 1192, [206, 209, 210, null]], +[209, ["number", 90], 97, 1192, [208, null]], +[210, ["number", 10], 97, 1234, [208, null]], +[211, ["vspace", 1], 21, 1294, [204, null]], [212, "hat", 38, 540, [null, 213, 261]], -[213, ["string", "card"], 96, 548, [212, null]], -[214, "stack", 44, 475, [259, 215, 260]], -[215, ["string", "card"], 102, 475, [214, null]], -[216, "xcor", 177, 909, [220, null]], -[217, "ycor", 177, 993, [222, null]], -[218, "box", 167, 1119, [224, 219, null]], -[219, ["string", "x"], 222, 1119, [218, null]], -[220, ["storein", 0], 109, 867, [141, 221, 216, 222]], -[221, ["string", "x"], 177, 867, [220, null]], -[222, ["storein", 0], 109, 951, [220, 223, 217, 49]], -[223, ["string", "y"], 177, 951, [222, null]], -[224, ["setxy2", 0], 109, 1119, [151, 218, 225, 147]], -[225, "box", 167, 1161, [224, 226, null]], -[226, ["string", "y"], 222, 1161, [225, null]], -[227, "ifelse", 44, 559, [260, 230, 251, 255, 238]], -[228, ["vspace", 200], 44, 885, [141, 154]], -[229, ["vspace", 40], 392, 713, [237, 253]], -[230, ["equal2", 0], 110, 525, [227, 232, 231, null]], -[231, ["number", 1], 156, 567, [230, null]], -[232, "box", 156, 525, [230, 233, null]], -[233, ["string", "number"], 211, 525, [232, null]], -[234, ["setxy2", 0], 124, 669, [251, 235, 236, 254]], -[235, ["number", 0], 182, 669, [234, null]], -[236, ["number", 0], 182, 711, [234, null]], -[237, "ifelse", 312, 645, [255, 239, 229, 249, null]], -[238, ["vspace", 60], 44, 645, [227, 141]], -[239, ["equal2", 0], 378, 611, [237, 241, 240, null]], -[240, ["number", 2], 424, 653, [239, null]], -[241, "box", 424, 611, [239, 242, null]], -[242, ["string", "number"], 479, 611, [241, null]], -[243, ["setxy2", 0], 392, 877, [253, 244, 245, 252]], -[244, ["number", -100], 450, 877, [243, null]], -[245, ["number", 0], 450, 919, [243, null]], -[246, "showblocks", 34, 334, [176, null]], -[247, "penup", 38, 834, [195, 192]], -[248, "pendown", 38, 960, [192, 204]], -[249, "penup", 444, 713, [237, 144]], -[250, "pendown", 444, 839, [144, null]], -[251, "penup", 124, 627, [227, 234]], -[252, "pendown", 392, 961, [243, null]], -[253, "penup", 392, 835, [229, 243]], -[254, "pendown", 124, 753, [234, null]], -[255, ["hspace", 40], 176, 627, [227, 237]], -[256, "sandwichtop_no_arm_no_label", 573, 574, [137, 156]], -[257, "sandwichtop_no_arm_no_label", 577, 417, [2, 86]], -[258, "sandwichtop_no_arm_no_label", 581, 263, [0, 32]], -[259, "sandwichtop_no_arm_no_label", 26, 441, [185, 214]], -[260, ["vspace", 0], 44, 517, [214, 227]], -[261, "sandwichtop_no_arm_no_label", 20, 590, [212, 128]], -[262, "sandwichtop_no_arm_no_label", 297, 253, [84, 61]], -[263, "sandwichtop_no_arm_no_label", 303, 399, [80, 35]], -[264, "sandwichtop_no_arm_no_label", 301, 548, [132, 134]], -[265, "sandwichtop_no_arm_no_label", 300, 708, [78, 38]], -[266, "seth", 591, 1322, [165, 267, 268]], -[267, ["number", "30"], 690, 1322, [266, null]], -[268, "forward", 591, 1364, [266, 269, 270]], -[269, ["number", 100], 662, 1364, [268, null]], -[270, "right", 591, 1406, [268, 271, 272]], -[271, ["number", "120"], 649, 1406, [270, null]], -[272, "forward", 591, 1448, [270, 273, 274]], -[273, ["number", 100], 662, 1448, [272, null]], -[274, "right", 591, 1490, [272, 275, 276]], -[275, ["number", "60"], 649, 1490, [274, null]], -[276, "forward", 591, 1532, [274, 277, 278]], -[277, ["number", 100], 662, 1532, [276, null]], -[278, "right", 591, 1574, [276, 279, 280]], -[279, ["number", "120"], 649, 1574, [278, null]], -[280, "forward", 591, 1616, [278, 281, 168]], -[281, ["number", 100], 662, 1616, [280, null]], -[282, "seth", 591, 902, [161, 283, 284]], -[283, ["number", 30], 690, 902, [282, null]], -[284, "forward", 591, 944, [282, 285, 286]], -[285, ["number", 100], 662, 944, [284, null]], -[286, "right", 591, 986, [284, 287, 288]], -[287, ["number", 120], 649, 986, [286, null]], -[288, "forward", 591, 1028, [286, 289, 290]], -[289, ["number", 100], 662, 1028, [288, null]], -[290, "right", 591, 1070, [288, 291, 292]], -[291, ["number", 60], 649, 1070, [290, null]], -[292, "forward", 591, 1112, [290, 293, 294]], -[293, ["number", 100], 662, 1112, [292, null]], -[294, "right", 591, 1154, [292, 295, 296]], -[295, ["number", 120], 649, 1154, [294, null]], -[296, "forward", 591, 1196, [294, 297, 163]], -[297, ["number", 100], 662, 1196, [296, null]]]
\ No newline at end of file +[213, ["string", "card"], 96, 552, [212, null]], +[214, "stack", 54, 516, [259, 215, 260]], +[215, ["string", "card"], 112, 516, [214, null]], +[216, "xcor", 141, 1574, [220, null]], +[217, "ycor", 141, 1658, [222, null]], +[218, "box", 130, 1784, [224, 219, null]], +[219, ["string", "x"], 184, 1784, [218, null]], +[220, ["storein", 0], 72, 1532, [141, 221, 216, 222]], +[221, ["string", "x"], 141, 1532, [220, null]], +[222, ["storein", 0], 72, 1616, [220, 223, 217, 49]], +[223, ["string", "y"], 141, 1616, [222, null]], +[224, ["setxy2", 0], 72, 1784, [151, 218, 225, 147]], +[225, "box", 130, 1826, [224, 226, null]], +[226, ["string", "y"], 184, 1826, [225, null]], +[227, ["ifelse", [63, 268]], +54, 600, [260, 230, 251, 255, 238]], +[228, ["vspace", 200], 54, 2012, [141, 154]], +[229, ["vspace", 40], 226, 936, [237, 253]], +[230, ["equal2", 0], 110, 566, [227, 232, 231, null]], +[231, ["number", 1], 166, 608, [230, null]], +[232, "box", 166, 566, [230, 233, null]], +[233, ["string", "number"], 220, 566, [232, null]], +[234, ["setxy2", 0], 72, 708, [251, 235, 236, 254]], +[235, ["number", 0], 130, 708, [234, null]], +[236, ["number", 0], 130, 750, [234, null]], +[237, ["ifelse", [124, 63]], +208, 870, [255, 239, 229, 249, null]], +[238, ["vspace", 0], 54, 1448, [227, 141]], +[239, ["equal2", 0], 264, 836, [237, 241, 240, null]], +[240, ["number", 2], 320, 878, [239, null]], +[241, "box", 320, 836, [239, 242, null]], +[242, ["string", "number"], 374, 836, [241, null]], +[243, ["setxy2", 0], 226, 1100, [253, 244, 245, 252]], +[244, ["number", -100], 284, 1100, [243, null]], +[245, ["number", 0], 284, 1142, [243, null]], +[246, "showblocks", 34, 338, [176, null]], +[247, "penup", 21, 814, [195, 192]], +[248, "pendown", 21, 940, [192, 204]], +[249, "penup", 226, 1244, [237, 144]], +[250, "pendown", 226, 1370, [144, null]], +[251, "penup", 72, 666, [227, 234]], +[252, "pendown", 226, 1184, [243, null]], +[253, "penup", 226, 1058, [229, 243]], +[254, "pendown", 72, 792, [234, null]], +[255, ["hspace", 40], 72, 852, [227, 237]], +[256, "sandwichclampcollapsed", 591, 578, [137, 156, null]], +[257, "sandwichclampcollapsed", 595, 421, [2, 86, null]], +[258, "sandwichclampcollapsed", 599, 267, [0, 32, null]], +[259, "sandwichclampcollapsed", 36, 482, [185, 214, null]], +[260, ["vspace", 0], 54, 558, [214, 227]], +[261, "sandwichclampcollapsed", 38, 594, [212, 128, null]], +[262, "sandwichclampcollapsed", 315, 257, [84, 61, null]], +[263, "sandwichclampcollapsed", 321, 403, [80, 35, null]], +[264, "sandwichclampcollapsed", 319, 552, [132, 134, null]], +[265, "sandwichclampcollapsed", 318, 712, [78, 38, null]], +[266, "seth", 574, 1302, [165, 267, 268]], +[267, ["number", 30], 676, 1302, [266, null]], +[268, "forward", 574, 1344, [266, 269, 270]], +[269, ["number", 100], 646, 1344, [268, null]], +[270, "right", 574, 1386, [268, 271, 272]], +[271, ["number", 120], 632, 1386, [270, null]], +[272, "forward", 574, 1428, [270, 273, 274]], +[273, ["number", 100], 646, 1428, [272, null]], +[274, "right", 574, 1470, [272, 275, 276]], +[275, ["number", 60], 632, 1470, [274, null]], +[276, "forward", 574, 1512, [274, 277, 278]], +[277, ["number", 100], 646, 1512, [276, null]], +[278, "right", 574, 1554, [276, 279, 280]], +[279, ["number", 120], 632, 1554, [278, null]], +[280, "forward", 574, 1596, [278, 281, 168]], +[281, ["number", 100], 646, 1596, [280, null]], +[282, "seth", 574, 882, [161, 283, 284]], +[283, ["number", 30], 676, 882, [282, null]], +[284, "forward", 574, 924, [282, 285, 286]], +[285, ["number", 100], 646, 924, [284, null]], +[286, "right", 574, 966, [284, 287, 288]], +[287, ["number", 120], 632, 966, [286, null]], +[288, "forward", 574, 1008, [286, 289, 290]], +[289, ["number", 100], 646, 1008, [288, null]], +[290, "right", 574, 1050, [288, 291, 292]], +[291, ["number", 60], 632, 1050, [290, null]], +[292, "forward", 574, 1092, [290, 293, 294]], +[293, ["number", 100], 646, 1092, [292, null]], +[294, "right", 574, 1134, [292, 295, 296]], +[295, ["number", 120], 632, 1134, [294, null]], +[296, "forward", 574, 1176, [294, 297, 163]], +[297, ["number", 100], 646, 1176, [296, null]], +[298, "int", 184, 1742, [64, 59]], +[-1, ["turtle", "Yertle"], 0.0, 0.0, 0.0, 0, 50, 5]]
\ No newline at end of file diff --git a/turtleblocks.py b/turtleblocks.py index 24b6343..7af1752 100755 --- a/turtleblocks.py +++ b/turtleblocks.py @@ -53,10 +53,12 @@ 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.tapalette import default_values from TurtleArt.tawindow import TurtleArtWindow from TurtleArt.taexportlogo import save_logo +from TurtleArt.taexportpython import save_python from util.menubuilder import MenuBuilder @@ -407,6 +409,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')) @@ -558,6 +562,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/ast_extensions.py b/util/ast_extensions.py new file mode 100644 index 0000000..3335afb --- /dev/null +++ b/util/ast_extensions.py @@ -0,0 +1,52 @@ +#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. + + +""" Extend the `ast` module to include comments """ + +import ast + + +class Comment(ast.stmt): + """ An inline comment, starting with a hashtag (#). + Extends the Python abstract grammar by the following: + stmt = Comment(string text) | ... """ + + _fields = ('text') + + def __init__(self, text="", lineno=1, col_offset=0): + """ text -- the textual content of the comment, i.e. everything + directly following the hashtag until the next newline """ + self.text = text + self.lineno = lineno + self.col_offset = col_offset + + +class LambdaWithStrBody(ast.Lambda): + """ Lambda AST whose body is a simple string (not ast.Str). + Extends the Python abstract grammar by the following: + expr = LambdaWithStrBody(string body_str, expr* args) | ... """ + + def __init__(self, body_str="", args=[], lineno=1, col_offset=0): + self.body_str = body_str + self.args = args + self.lineno = lineno + self.col_offset = col_offset + diff --git a/util/codegen.py b/util/codegen.py new file mode 100644 index 0000000..3785085 --- /dev/null +++ b/util/codegen.py @@ -0,0 +1,593 @@ +# -*- 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 * +from ast_extensions import Comment + + +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 use parentheses around expressions only where necessary + 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) + 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') + if hasattr(node, "value") and node.value is not None: + self.write(' ') + 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) + + def visit_Comment(self, node): + self.newline(node) + self.write('#' + str(node.text)) + + # 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(')') + visit_TypedCall = visit_Call + + def visit_Name(self, node): + self.write(node.id) + visit_TypedName = visit_Name + + 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) + 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.visit(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(']') + visit_TypedSubscript = visit_Subscript + + def visit_Index(self, node): + self.visit(node.value) + + 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) + self.write(')') + + def visit_LambdaWithStrBody(self, node): + self.write('(lambda ') + for idx, arg in enumerate(node.args): + if idx: + self.write(', ') + self.visit(arg) + self.write(': ') + self.write(node.body_str) + self.write(')') + + 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) |