Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TurtleArt/tabasics.py967
-rw-r--r--TurtleArt/tablock.py86
-rw-r--r--TurtleArt/tacanvas.py20
-rw-r--r--TurtleArt/taconstants.py108
-rw-r--r--TurtleArt/taexportlogo.py14
-rw-r--r--TurtleArt/taexportpython.py259
-rw-r--r--TurtleArt/tajail.py20
-rw-r--r--TurtleArt/talogo.py495
-rw-r--r--TurtleArt/taprimitive.py1105
-rw-r--r--TurtleArt/taturtle.py57
-rw-r--r--TurtleArt/tatype.py439
-rw-r--r--TurtleArt/tautils.py32
-rw-r--r--TurtleArt/tawindow.py122
-rw-r--r--TurtleArtActivity.py45
-rw-r--r--doc/primitives-with-arguments.md125
-rw-r--r--doc/type-system.md116
-rw-r--r--icons/python-saveoff.svg111
-rw-r--r--icons/python-saveon.svg111
-rw-r--r--plugins/camera_sensor/camera_sensor.py5
-rw-r--r--plugins/turtle_blocks_extras/turtle_blocks_extras.py344
-rw-r--r--pyexported/__init__.py0
-rw-r--r--pyexported/window_setup.py138
-rw-r--r--samples/game-set.ta582
-rwxr-xr-xturtleblocks.py32
-rw-r--r--util/ast_extensions.py52
-rw-r--r--util/codegen.py593
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)