Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/TurtleArt
diff options
context:
space:
mode:
authorWalter Bender <walter@sugarlabs.org>2013-11-13 22:42:18 (GMT)
committer Walter Bender <walter@sugarlabs.org>2013-11-13 22:42:18 (GMT)
commit6acdbc3db543f2692ee336a99722f5ab0b46c77e (patch)
tree97b84e77c63bddeb49bcb804e25e2457008f6a8d /TurtleArt
parent3865e3c912f70fd7fcef2d20831a0231d30d96f1 (diff)
convert to new primitive type
Diffstat (limited to 'TurtleArt')
-rw-r--r--TurtleArt/tabasics.py1053
-rw-r--r--TurtleArt/tablock.py93
-rw-r--r--TurtleArt/tacanvas.py20
-rw-r--r--TurtleArt/taconstants.py219
-rw-r--r--TurtleArt/taexportpython.py282
-rw-r--r--TurtleArt/tajail.py28
-rw-r--r--TurtleArt/talogo.py629
-rw-r--r--TurtleArt/tapalette.py3
-rw-r--r--TurtleArt/taprimitive.py1162
-rw-r--r--TurtleArt/taturtle.py119
-rw-r--r--TurtleArt/tatype.py443
-rw-r--r--TurtleArt/tautils.py41
-rw-r--r--TurtleArt/tawindow.py340
13 files changed, 3477 insertions, 955 deletions
diff --git a/TurtleArt/tabasics.py b/TurtleArt/tabasics.py
index 3d1eb10..3d88ce4 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, debug_output)
-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()
@@ -119,13 +150,11 @@ class Palettes():
def _turtle_palette(self):
''' The basic Turtle Art turtle palette '''
- debug_output('creating %s palette' % _('turtle'),
- self.tw.running_sugar)
palette = make_palette('turtle',
colors=["#00FF00", "#00A000"],
- help_string=_('Palette of turtle commands'))
+ help_string=_('Palette of turtle commands'),
+ translation=_('turtle'))
- primitive_dictionary['move'] = self._prim_move
palette.add_block('forward',
style='basic-style-1arg',
label=_('forward'),
@@ -134,10 +163,10 @@ class Palettes():
logo_command='forward',
help_string=_('moves turtle forward'))
self.tw.lc.def_prim(
- 'forward',
- 1,
- lambda self, x: primitive_dictionary['move'](
- self.tw.turtles.get_active_turtle().forward, x))
+ 'forward', 1,
+ Primitive(Turtle.forward,
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=self.after_move))
palette.add_block('back',
style='basic-style-1arg',
@@ -146,13 +175,12 @@ class Palettes():
default=100,
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))
+ self.tw.lc.def_prim(
+ 'back', 1,
+ Primitive(Turtle.backward,
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=self.after_move))
- primitive_dictionary['clean'] = self._prim_clear
palette.add_block('clean',
style='basic-style-extended-vertical',
label=_('clean'),
@@ -160,12 +188,19 @@ 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'),
@@ -175,8 +210,10 @@ 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.left,
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=self.after_right))
palette.add_block('right',
style='basic-style-1arg',
@@ -187,11 +224,11 @@ in degrees)'))
help_string=_('turns turtle clockwise (angle in \
degrees)'))
self.tw.lc.def_prim(
- 'right',
- 1,
- lambda self, x: primitive_dictionary['right'](x))
+ 'right', 1,
+ 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')],
@@ -200,10 +237,11 @@ degrees)'))
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))
+ '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')
@@ -216,13 +254,12 @@ degrees)'))
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))
+ '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'),
@@ -232,10 +269,11 @@ degrees)'))
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))
+ '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',
@@ -246,10 +284,11 @@ the turtle (can be used in place of a number block)'),
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)
+ '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',
@@ -260,10 +299,11 @@ the turtle (can be used in place of a number block)'),
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)
+ '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',
@@ -273,16 +313,14 @@ 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))
- # This block is used for holding the remote turtle name
palette.add_block('turtle-label',
hidden=True,
style='blank-style',
- label=['remote turtle name'])
+ label=['turtle'])
# Deprecated
palette.add_block('setxy',
@@ -295,22 +333,21 @@ turtle (can be used in place of a number block)'),
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))
+ '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')
def _pen_palette(self):
''' The basic Turtle Art pen palette '''
- debug_output('creating %s palette' % _('pen'),
- self.tw.running_sugar)
palette = make_palette('pen',
colors=["#00FFFF", "#00A0A0"],
- help_string=_('Palette of pen commands'))
+ help_string=_('Palette of pen commands'),
+ translation=_('pen'))
palette.add_block('fillscreen',
hidden=True,
@@ -322,9 +359,9 @@ setxy :x :y\npendown\nend\n')
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))
+ 'fillscreen', 2,
+ Primitive(self.tw.canvas.fillscreen,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)]))
palette.add_block('fillscreen2',
style='basic-style-3arg',
@@ -336,9 +373,10 @@ shade)'))
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))
+ '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')
@@ -352,10 +390,12 @@ shade)'))
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))
+ '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',
@@ -366,10 +406,11 @@ turtle'))
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))
+ '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',
@@ -379,10 +420,11 @@ turtle'))
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))
+ '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',
@@ -392,10 +434,9 @@ 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',
@@ -404,10 +445,7 @@ in place of a number block)'),
value_block=True,
prim_name='shade',
logo_command=':shade')
- self.tw.lc.def_prim(
- 'shade',
- 0,
- lambda self: self.tw.turtles.get_active_turtle().get_shade())
+ self.tw.lc.def_prim('shade', 0, Primitive(Turtle.get_shade))
palette.add_block('gray',
style='box-style',
@@ -416,8 +454,9 @@ 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',
@@ -426,10 +465,8 @@ used in place of a number block)'),
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))
+ 'penup', 0,
+ Primitive(Turtle.set_pen_state, arg_descs=[ConstantArg(False)]))
palette.add_block('pendown',
style='basic-style-extended-vertical',
@@ -438,10 +475,8 @@ used in place of a number block)'),
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))
+ 'pendown', 0,
+ Primitive(Turtle.set_pen_state, arg_descs=[ConstantArg(True)]))
palette.add_block('penstate',
style='boolean-block-style',
@@ -449,9 +484,8 @@ used in place of a number block)'),
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())
+ 'penstate', 0,
+ Primitive(Turtle.get_pen_state, return_type=TYPE_BOOL))
palette.add_block('setpensize',
style='basic-style-1arg',
@@ -463,8 +497,10 @@ used in place of a number block)'),
turtle'))
self.tw.lc.def_prim(
'setpensize', 1,
- lambda self, x: primitive_dictionary['set']
- ('pensize', self.tw.turtles.get_active_turtle().set_pen_size, x))
+ Primitive(Turtle.set_pen_size,
+ 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')
@@ -474,10 +510,7 @@ 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',
@@ -485,10 +518,7 @@ 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',
@@ -499,33 +529,27 @@ in place of a number block)'),
prim_name='pensize',
logo_command='pensize')
self.tw.lc.def_prim(
- 'pensize',
- 0,
- lambda self: self.tw.turtles.get_active_turtle().get_pen_size())
+ 'pensize', 0,
+ Primitive(Turtle.get_pen_size, return_type=TYPE_NUMBER))
define_logo_function('tapensize', 'to tapensize\noutput first round \
pensize\nend\n')
def _color_palette(self):
''' The basic Turtle Art color palette '''
- debug_output('creating %s palette' % _('colors'),
- self.tw.running_sugar)
palette = make_palette('colors',
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'])
+ help_string=_('Palette of pen colors'),
+ translation=_('colors'))
+
+ color_names = ('red', 'orange', 'yellow', 'green', 'cyan', 'blue',
+ 'purple', 'white', 'black')
+ # Need to make sure color names are included in the PO files
+ color_names_i18n = (_('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.
@@ -603,13 +627,11 @@ tasetshade :shade \n')
def _numbers_palette(self):
''' The basic Turtle Art numbers palette '''
- debug_output('creating %s palette' % _('numbers'),
- self.tw.running_sugar)
palette = make_palette('numbers',
colors=["#FF00FF", "#A000A0"],
- help_string=_('Palette of numeric operators'))
+ help_string=_('Palette of numeric operators'),
+ translation=_('numbers'))
- primitive_dictionary['plus'] = self._prim_plus
palette.add_block('plus2',
style='number-style',
label='+',
@@ -619,9 +641,16 @@ tasetshade :shade \n')
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))
+ '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=' –',
@@ -631,11 +660,12 @@ tasetshade :shade \n')
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))
+ '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='×',
@@ -645,9 +675,9 @@ minus :y\nend\n')
help_string=_('multiplies two numeric inputs'))
self.tw.lc.def_prim(
'product', 2,
- lambda self, x, y: primitive_dictionary['product'](x, y))
+ 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=' /',
@@ -658,9 +688,9 @@ minus :y\nend\n')
(numerator) by bottom numeric input (denominator)'))
self.tw.lc.def_prim(
'division', 2,
- lambda self, x, y: primitive_dictionary['division'](x, y))
+ 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='←',
@@ -668,10 +698,26 @@ minus :y\nend\n')
prim_name='id',
help_string=_('identity operator used for extending \
blocks'))
- self.tw.lc.def_prim('id', 1,
- lambda self, x: primitive_dictionary['id'](x))
+ self.tw.lc.def_prim(
+ 'id', 1,
+ # 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'),
@@ -679,11 +725,11 @@ blocks'))
prim_name='remainder',
logo_command='remainder',
help_string=_('modular (remainder) operator'))
- self.tw.lc.def_prim('remainder', 2,
- lambda self, x, y:
- primitive_dictionary['remainder'](x, y))
+ self.tw.lc.def_prim(
+ 'remainder', 2,
+ 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=_('√'),
@@ -691,10 +737,11 @@ blocks'))
prim_name='sqrt',
logo_command='tasqrt',
help_string=_('calculates square root'))
- self.tw.lc.def_prim('sqrt', 1,
- lambda self, x: primitive_dictionary['sqrt'](x))
+ self.tw.lc.def_prim(
+ 'sqrt', 1,
+ 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')],
@@ -703,9 +750,19 @@ 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))
+ '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')
@@ -717,7 +774,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=' >',
@@ -728,9 +784,16 @@ operators'))
help_string=_('logical greater-than operator'))
self.tw.lc.def_prim(
'greater?', 2,
- lambda self, x, y: primitive_dictionary['more'](x, y))
+ 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=' <',
@@ -740,9 +803,17 @@ operators'))
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))
+ '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='=',
@@ -751,9 +822,17 @@ operators'))
prim_name='equal?',
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))
+ self.tw.lc.def_prim(
+ 'equal?', 2,
+ 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',
@@ -761,9 +840,11 @@ 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'),
@@ -772,9 +853,10 @@ operators'))
special_name=_('and'),
help_string=_('logical AND operator'))
self.tw.lc.def_prim(
- 'and', 2, lambda self, x, y: primitive_dictionary['and'](x, y))
+ 'and', 2,
+ Primitive(Primitive.and_, 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'),
@@ -783,18 +865,18 @@ operators'))
special_name=_('or'),
help_string=_('logical OR operator'))
self.tw.lc.def_prim(
- 'or', 2, lambda self, x, y: primitive_dictionary['or'](x, y))
+ 'or', 2,
+ Primitive(Primitive.or_, return_type=TYPE_BOOL,
+ arg_descs=[ArgSlot(TYPE_BOOL), ArgSlot(TYPE_BOOL)]))
def _flow_palette(self):
''' The basic Turtle Art flow palette '''
- debug_output('creating %s palette' % _('flow'),
- self.tw.running_sugar)
palette = make_palette('flow',
colors=["#FFC000", "#A08000"],
- help_string=_('Palette of flow operators'))
+ help_string=_('Palette of flow operators'),
+ translation=_('flow'))
- primitive_dictionary['wait'] = self._prim_wait
palette.add_block('wait',
style='basic-style-1arg',
label=_('wait'),
@@ -803,9 +885,11 @@ 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'),
@@ -813,10 +897,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'),
@@ -825,9 +912,16 @@ 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'), ''],
@@ -837,9 +931,12 @@ 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',
@@ -850,7 +947,12 @@ 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',
@@ -867,7 +969,9 @@ 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',
@@ -875,28 +979,28 @@ 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'),
prim_name='stopstack',
logo_command='stop',
help_string=_('stops current action'))
- self.tw.lc.def_prim('stopstack', 0,
- lambda self: primitive_dictionary['stopstack']())
+ self.tw.lc.def_prim(
+ 'stopstack', 0,
+ Primitive(self.tw.lc.prim_stop_stack))
def _blocks_palette(self):
''' The basic Turtle Art blocks palette '''
- debug_output('creating %s palette' % _('blocks'),
- self.tw.running_sugar)
palette = make_palette('blocks',
colors=["#FFFF00", "#A0A000"],
- help_string=_('Palette of variable blocks'))
+ help_string=_('Palette of variable blocks'),
+ translation=_('blocks'))
- primitive_dictionary['start'] = self._prim_start
palette.add_block('start',
style='basic-style-head',
label=_('start'),
@@ -904,8 +1008,13 @@ boolean operators from Numbers palette'))
logo_command='to start\n',
help_string=_('connects action to toolbar run \
buttons'))
- self.tw.lc.def_prim('start', 0,
- lambda self: primitive_dictionary['start']())
+ self.tw.lc.def_prim(
+ 'start', 0,
+ 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',
@@ -922,9 +1031,14 @@ 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_OBJECT)]))
- primitive_dictionary['stack'] = self._prim_stack
+ primitive_dictionary['stack'] = Primitive(
+ self.tw.lc.prim_invoke_stack,
+ arg_descs=[ArgSlot(TYPE_OBJECT)])
palette.add_block('stack',
style='basic-style-1arg',
label=_('action'),
@@ -933,9 +1047,9 @@ buttons'))
logo_command='action',
default=_('action'),
help_string=_('invokes named action stack'))
- self.tw.lc.def_prim('stack', 1, primitive_dictionary['stack'], True)
+ 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',
@@ -945,10 +1059,10 @@ buttons'))
string_or_number=True,
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))
+ self.tw.lc.def_prim(
+ 'storeinbox1', 1,
+ Primitive(self.tw.lc.prim_set_box,
+ arg_descs=[ConstantArg('box1'), ArgSlot(TYPE_OBJECT)]))
palette.add_block('storeinbox2',
hidden=True,
@@ -959,10 +1073,10 @@ buttons'))
string_or_number=True,
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))
+ self.tw.lc.def_prim(
+ 'storeinbox2', 1,
+ Primitive(self.tw.lc.prim_set_box,
+ arg_descs=[ConstantArg('box2'), ArgSlot(TYPE_OBJECT)]))
palette.add_block('box1',
hidden=True,
@@ -972,7 +1086,10 @@ 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,
@@ -982,8 +1099,14 @@ 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')],
@@ -993,12 +1116,12 @@ 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,
@@ -1009,8 +1132,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,
@@ -1019,7 +1141,10 @@ 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,
@@ -1028,9 +1153,11 @@ 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',
@@ -1038,9 +1165,12 @@ 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',
@@ -1048,16 +1178,19 @@ 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 '''
- debug_output('creating %s palette' % _('trash'),
- self.tw.running_sugar)
palette = make_palette('trash',
colors=["#FFFF00", "#A0A000"],
- help_string=_('trash'))
+ help_string=_('trash'),
+ translation=_('trash'))
palette.add_block('empty',
style='blank-style',
@@ -1074,19 +1207,9 @@ variable'))
label=_('clear all'),
help_string=_('move all blocks to trash'))
- # Block primitives
+ # Callbacks to update labels after executing a block
- 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
-
- 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',
@@ -1100,65 +1223,8 @@ variable'))
'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',
@@ -1169,323 +1235,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..cd5515e 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, BLOCKS_WITH_SKIN)
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,13 +605,12 @@ 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))
+ if self.name not in BLOCKS_WITH_SKIN:
+ self._set_labels(0, block_names[self.name][0] + ' = ' + str(v))
elif self.name in block_names:
for i, n in enumerate(block_names[self.name]):
@@ -561,11 +636,11 @@ class Block:
if n == 0:
n = 1 # Force a scale to be set, even if there is no value.
else:
+ n = 0
if self.name in block_names:
n = len(block_names[self.name])
- else:
+ elif self.name not in BLOCKS_WITH_SKIN:
debug_output('WARNING: unknown block name %s' % (self.name))
- n = 0
for i in range(n):
if i > 0:
size = int(self.font_size[1] + 0.5)
@@ -973,7 +1048,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..4cedb34 100644
--- a/TurtleArt/taconstants.py
+++ b/TurtleArt/taconstants.py
@@ -79,20 +79,173 @@ XO4 = 'xo4'
UNKNOWN = 'unknown'
TMP_SVG_PATH = '/tmp/turtle_output.svg'
+ARG_MUST_BE_NUMBER = ['product2', 'minus2', 'random', 'remainder2', 'forward',
+ 'back', 'left', 'right', 'arc', 'setxy2', 'setxy',
+ 'fillscreen', 'setscale', 'setpensize', 'wait',
+ 'setcolor', 'seth', 'setgray', 'setshade', 'string',
+ 'fillscreen2']
+
+KEY_DICT = {
+ 'Left': 1,
+ 'KP_Left': 1,
+ 'Up': 2,
+ 'KP_Up': 2,
+ 'Right': 3,
+ 'KP_Right': 3,
+ 'Down': 4,
+ 'KP_Down': 4,
+ 'BackSpace': 8,
+ 'Tab': 9,
+ 'Return': 13,
+ 'Escape': 27,
+ 'space': 32,
+ ' ': 32,
+ 'exclam': 33,
+ 'quotedbl': 34,
+ 'numbersign': 35,
+ 'dollar': 36,
+ 'percent': 37,
+ 'ampersand': 38,
+ 'apostrophe': 39,
+ 'parenleft': 40,
+ 'parenright': 41,
+ 'asterisk': 42,
+ 'plus': 43,
+ 'comma': 44,
+ 'minus': 45,
+ 'period': 46,
+ 'slash': 47,
+ 'colon': 58,
+ 'semicolon': 59,
+ 'less': 60,
+ 'equal': 61,
+ 'greater': 62,
+ 'question': 63,
+ 'at': 64,
+ 'underscore': 95,
+ 'bracketleft': 91,
+ 'backslash': 92,
+ 'bracketright': 93,
+ 'asciicircum': 94,
+ 'grave': 96,
+ 'braceleft': 123,
+ 'bar': 124,
+ 'braceright': 125,
+ 'asciitilde': 126,
+ 'Delete': 127,
+ }
+REVERSE_KEY_DICT = {
+ 1: _('left'),
+ 2: _('up'),
+ 3: _('right'),
+ 4: _('down'),
+ 8: _('backspace'),
+ 9: _('tab'),
+ # TRANS: enter is the name of the enter (or return) key
+ 13: _('enter'),
+ 27: 'esc',
+ # TRANS: space is the name of the space key
+ 32: _('space'),
+ 127: _('delete')
+ }
+
+
+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 +266,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 = []
@@ -197,7 +350,7 @@ MACROS = {
[5, ['number', '0.1'], 0, 0, [4, None]],
[6, 'kbinput', 0, 0, [4, None]]],
'picturelist':
- [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [[0, ['sandwichclamp', 252], 0, 0, [None, 1, None]],
[1, 'penup', 0, 0, [0, 2]],
[2, 'setxy2', 0, 0, [1, 3, 4, 5]],
[3, 'titlex', 0, 0, [2, None]],
@@ -214,12 +367,11 @@ MACROS = {
[14, 'pendown', 0, 0, [11, 15]],
[15, 'setscale', 0, 0, [14, 16, 17]],
[16, ['number', '67'], 0, 0, [15, None]],
- [17, 'list', 0, 0, [15, 18, 19, 20]],
+ [17, 'list', 0, 0, [15, 18, 19, None]],
[18, ['string', '∙ '], 0, 0, [17, None]],
- [19, ['string', '∙ '], 0, 0, [17, None]],
- [20, 'sandwichbottom', 0, 0, [17, None]]],
+ [19, ['string', '∙ '], 0, 0, [17, None]]],
'picture1x1a':
- [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [[0, ['sandwichclamp', 231], 0, 0, [None, 1, None]],
[1, 'penup', 0, 0, [0, 2]],
[2, 'setxy2', 0, 0, [1, 3, 4, 5]],
[3, 'titlex', 0, 0, [2, None]],
@@ -236,11 +388,10 @@ MACROS = {
[14, 'pendown', 0, 0, [11, 15]],
[15, 'setscale', 0, 0, [14, 16, 17]],
[16, ['number', '90'], 0, 0, [15, None]],
- [17, 'showaligned', 0, 0, [15, 18, 19]],
- [18, 'journal', 0, 0, [17, None]],
- [19, 'sandwichbottom', 0, 0, [17, None]]],
+ [17, 'showaligned', 0, 0, [15, 18, None]],
+ [18, 'journal', 0, 0, [17, None]]],
'picture2x2':
- [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [[0, ['sandwichclamp', 546], 0, 0, [None, 1, None]],
[1, 'penup', 0, 0, [0, 2]],
[2, 'setxy2', 0, 0, [1, 3, 4, 5]],
[3, 'titlex', 0, 0, [2, None]],
@@ -278,11 +429,10 @@ MACROS = {
[35, 'rightx', 0, 0, [34, None]],
[36, 'bottomy', 0, 0, [34, None]],
[37, 'pendown', 0, 0, [34, 38]],
- [38, 'showaligned', 0, 0, [37, 39, 40]],
- [39, 'journal', 0, 0, [38, None]],
- [40, 'sandwichbottom', 0, 0, [38, None]]],
+ [38, 'showaligned', 0, 0, [37, 39, None]],
+ [39, 'journal', 0, 0, [38, None]]],
'picture1x2':
- [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [[0, ['sandwichclamp', 546], 0, 0, [None, 1, None]],
[1, 'penup', 0, 0, [0, 2]],
[2, 'setxy2', 0, 0, [1, 3, 4, 5]],
[3, 'titlex', 0, 0, [2, None]],
@@ -320,11 +470,10 @@ MACROS = {
[35, 'rightx', 0, 0, [34, None]],
[36, 'bottomy', 0, 0, [34, None]],
[37, 'pendown', 0, 0, [34, 38]],
- [38, 'showaligned', 0, 0, [37, 39, 40]],
- [39, 'description', 0, 0, [38, None]],
- [40, 'sandwichbottom', 0, 0, [38, None]]],
+ [38, 'showaligned', 0, 0, [37, 39, None]],
+ [39, 'description', 0, 0, [38, None]]],
'picture2x1':
- [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [[0, ['sandwichclamp', 546], 0, 0, [None, 1, None]],
[1, 'penup', 0, 0, [0, 2]],
[2, 'setxy2', 0, 0, [1, 3, 4, 5]],
[3, 'titlex', 0, 0, [2, None]],
@@ -362,11 +511,10 @@ MACROS = {
[35, 'rightx', 0, 0, [34, None]],
[36, 'bottomy', 0, 0, [34, None]],
[37, 'pendown', 0, 0, [34, 38]],
- [38, 'showaligned', 0, 0, [37, 39, 40]],
- [39, 'description', 0, 0, [38, None]],
- [40, 'sandwichbottom', 0, 0, [38, None]]],
+ [38, 'showaligned', 0, 0, [37, 39, None]],
+ [39, 'description', 0, 0, [38, None]]],
'picture1x1':
- [[0, 'sandwichtop_no_label', 0, 0, [None, 1]],
+ [[0, ['sandwichclamp', 336], 0, 0, [None, 1, None]],
[1, 'penup', 0, 0, [0, 2]],
[2, 'setxy2', 0, 0, [1, 3, 4, 5]],
[3, 'titlex', 0, 0, [2, None]],
@@ -390,9 +538,8 @@ MACROS = {
[21, 'rightx', 0, 0, [20, None]],
[22, 'topy', 0, 0, [20, None]],
[23, 'pendown', 0, 0, [20, 24]],
- [24, 'showaligned', 0, 0, [23, 25, 26]],
- [25, 'description', 0, 0, [24, None]],
- [26, 'sandwichbottom', 0, 0, [24, None]]],
+ [24, 'showaligned', 0, 0, [23, 25, None]],
+ [25, 'description', 0, 0, [24, None]]],
'reskin':
[[0, 'skin', 0, 0, [None, 1, None]],
[1, 'journal', 0, 0, [0, None]]]}
diff --git a/TurtleArt/taexportpython.py b/TurtleArt/taexportpython.py
new file mode 100644
index 0000000..435bd06
--- /dev/null
+++ b/TurtleArt/taexportpython.py
@@ -0,0 +1,282 @@
+#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 (find_group, find_top_block, get_stack_name)
+from tawindow import plugins_in_use
+
+
+_SETUP_CODE_START = """\
+#!/usr/bin/env python
+
+_INSTALL_PATH = '/usr/share/sugar/activities/TurtleArt.activity'
+_ALTERNATIVE_INSTALL_PATH = \
+ '/usr/local/share/sugar/activities/TurtleArt.activity'
+
+import os, sys
+if os.path.exists('../TurtleBlocks.activity'):
+ sys.path.insert(0, '../TurtleBlocks.activity')
+elif os.path.exists(os.path.join(os.path.expanduser('~'), 'Activities',
+ 'TurtleBlocks.activity')):
+ sys.path.insert(0, os.path.join(os.path.expanduser('~'), 'Activities',
+ 'TurtleBlocks.activity'))
+elif os.path.exists(_INSTALL_PATH):
+ sys.path.insert(0, _INSTALL_PATH)
+elif os.path.exists(_ALTERNATIVE_INSTALL_PATH):
+ sys.path.insert(0, _ALTERNATIVE_INSTALL_PATH)
+else:
+ print 'This code require the TurtleBlocks activity to be installed.'
+ exit(1)
+
+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()
+ global_objects = tw.get_global_objects()
+"""
+_ACTION_STACK_PREAMBLE = """\
+ turtles = tw.turtles
+ turtle = turtles.get_active_turtle()
+ 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, name=stack_name)
+ snippets.append(pythoncode)
+ snippets.append(linesep)
+ snippets.append(_SETUP_CODE_END)
+ return "".join(snippets)
+
+
+def _action_stack_to_python(block, tw, 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, tw.lc)
+ if not ast_list or not isinstance(ast_list[-1], ast.Yield):
+ ast_list.append(ast_yield_true())
+ action_stack_ast = ast.Module(body=ast_list)
+
+ # 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
+ for k in plugins_in_use:
+ pre_preamble += " %s = global_objects['%s']\n" % (k.lower(), k)
+ 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..1a89f1d 100644
--- a/TurtleArt/tajail.py
+++ b/TurtleArt/tajail.py
@@ -27,24 +27,14 @@ 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])
-
-
-def myfunc_import(parent, f, x):
+ 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, args):
''' Run Python code imported from Journal '''
if 'def myblock(lc,' in f:
base_class = parent.tw.lc # pre-v107, we passed lc
@@ -53,7 +43,7 @@ def myfunc_import(parent, f, x):
userdefined = {}
try:
exec f in globals(), userdefined
- return userdefined['myblock'](base_class, x)
+ return userdefined['myblock'](base_class, args)
except:
traceback.print_exc()
return None
diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py
index 0b178c6..775af0f 100644
--- a/TurtleArt/talogo.py
+++ b/TurtleArt/talogo.py
@@ -22,9 +22,12 @@
#THE SOFTWARE.
import gtk
+import gobject
from time import time, sleep
from operator import isNumberType
+import os
+from os.path import exists as os_path_exists
from UserDict import UserDict
try:
@@ -33,10 +36,17 @@ 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, ICON_SIZE)
+from tajail import (myfunc, myfunc_import)
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, get_stack_name,
+ text_media_type, round_int, debug_output, find_group,
+ get_path, image_to_base64, data_to_string, data_to_file,
+ get_load_name, chooser_dialog)
try:
from util.RtfParser import RtfTextOnly
@@ -46,7 +56,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 +88,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 +115,7 @@ class HiddenBlock:
self.connections = []
self.docks = []
+
# Utility functions
@@ -142,7 +166,6 @@ class LogoCode:
self.hidden_turtle = None
- self.keyboard = 0
self.trace = 0
self.update_values = False
self.gplay = None
@@ -187,6 +210,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().
"""
@@ -234,27 +265,17 @@ class LogoCode:
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 +299,8 @@ class LogoCode:
return ['%nothing%', '%nothing%']
code = []
dock = blk.docks[0]
- if len(dock) > 4: # There could be a '(', ')', '[' or ']'.
+ # There could be a '(', ')', '[' or ']'.
+ if len(dock) > 4 and dock[4] in ('[', ']', ']['):
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 +308,20 @@ 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 ']'.
+ # There could be a '(', ')', '[' or ']'.
+ if len(dock) > 4 and dock[4] in ('[', ']', ']['):
for c in dock[4]:
code.append(c)
if b is not None:
@@ -346,7 +351,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 +408,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 +439,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 +462,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 +474,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 +486,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 +496,53 @@ 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,7 +551,9 @@ 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:
@@ -534,23 +561,60 @@ class LogoCode:
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 +661,265 @@ 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 set_scale(self, scale):
+ ''' Set scale for media blocks '''
+ self.scale = scale
+
+ def get_scale(self):
+ ''' Set scale for media blocks '''
+ return self.scale
+
+ def prim_stop_stack(self):
+ """ Stop execution of a stack """
+ self.procstop = True
+
+ def prim_turtle(self, name):
+ self.tw.turtles.set_turtle(name)
+
+ 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:
+ # FIXME this looks like a syntax error in the GUI
+ raise logoerror("#emptybox")
+
+ 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, float)):
+ if int(name) == name:
+ name = int(name)
+ else:
+ name = float(name)
+ return 'stack3' + str(name)
+
+ def load_heap(self, path):
+ """ Load FILO from file """
+ if self.tw.running_sugar:
+ # Choose a datastore object and push data to heap (Sugar only)
+ chooser_dialog(self.tw.parent, path, self.push_file_data_to_heap)
+ else:
+ if not os.path.exists(path):
+ path, self.tw.load_save_folder = get_load_name(
+ '.*', self.tw.load_save_folder)
+ if path is None:
+ return
+
+ data = data_from_file(path)
+ if data is not None:
+ for val in data:
+ self.heap.append(val)
+
+ def save_heap(self, path):
+ """ save FILO to file """
+ if self.tw.running_sugar:
+ from sugar import profile
+ from sugar.datastore import datastore
+ from sugar.activity import activity
+
+ # Save JSON-encoded heap to temporary file
+ heap_file = os.path.join(get_path(activity, 'instance'),
+ str(path) + '.txt')
+ data_to_file(self.heap, heap_file)
+
+ # Create a datastore object
+ dsobject = datastore.create()
+
+ # Write any metadata (specifically set the title of the file
+ # and specify that this is a plain text file).
+ dsobject.metadata['title'] = str(path)
+ dsobject.metadata['icon-color'] = profile.get_color().to_string()
+ dsobject.metadata['mime_type'] = 'text/plain'
+ dsobject.set_file_path(heap_file)
+ datastore.write(dsobject)
+ dsobject.destroy()
+ else:
+ heap_file = path
+ data_to_file(self.heap, heap_file)
+
+ 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_myblock(self, *args):
+ """ Run Python code imported from Journal """
+ if self.bindex is not None and self.bindex in self.tw.myblock:
+ try:
+ myfunc_import(self, self.tw.myblock[self.bindex], args)
+ except:
+ raise logoerror("#syntaxerror")
+
+ 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 +996,145 @@ class LogoCode:
for blk in drag_group:
blk.spr.move_relative((dx, 0))
+ def reskin(self, obj):
+ """ Reskin the turtle with an image from a file """
+ scale = int(ICON_SIZE * float(self.scale) / DEFAULT_SCALE)
+ if scale < 1:
+ return
+ self.filepath = None
+ self.dsobject = None
+
+ if os_path_exists(obj.value): # file path
+ self.filepath = obj.value
+ elif self.tw.running_sugar: # datastore object
+ 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.filepath is None:
+ self.tw.showlabel('nojournal', self.filepath)
+ return
+
+ pixbuf = None
+ try:
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
+ self.filepath, scale, scale)
+ except:
+ self.tw.showlabel('nojournal', self.filepath)
+ debug_output("Couldn't open skin %s" % (self.filepath),
+ self.tw.running_sugar)
+ if pixbuf is not None:
+ self.tw.turtles.get_active_turtle().set_shapes([pixbuf])
+ pen_state = self.tw.turtles.get_active_turtle().get_pen_state()
+ if pen_state:
+ self.tw.turtles.get_active_turtle().set_pen_state(False)
+ self.tw.turtles.get_active_turtle().forward(0)
+ if pen_state:
+ self.tw.turtles.get_active_turtle().set_pen_state(True)
+
+ if self.tw.sharing():
+ if self.tw.running_sugar:
+ tmp_path = get_path(self.tw.activity, 'instance')
+ else:
+ tmp_path = '/tmp'
+ tmp_file = os.path.join(get_path(self.tw.activity, 'instance'),
+ 'tmpfile.png')
+ pixbuf.save(tmp_file, 'png', {'quality': '100'})
+ data = image_to_base64(tmp_file, tmp_path)
+ height = pixbuf.get_height()
+ width = pixbuf.get_width()
+ event = 'R|%s' % (data_to_string([self.tw.nick,
+ [round_int(width),
+ round_int(height),
+ data]]))
+ gobject.idle_add(self.tw.send_event, event)
+ os.remove(tmp_file)
+
+ def showlist(self, objects):
+ """ Display list of media objects """
+ x = (self.tw.turtles.get_active_turtle().get_xy()[0] /
+ self.tw.coord_scale)
+ y = (self.tw.turtles.get_active_turtle().get_xy()[1] /
+ self.tw.coord_scale)
+ for obj in objects:
+ self.tw.turtles.get_active_turtle().set_xy(x, y, pendown=False)
+ self.show(obj)
+ y -= int(self.tw.canvas.textsize * self.tw.lead)
+
+ 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)
@@ -861,7 +1310,7 @@ class LogoCode:
def _expand_forever(self, b, blk, blocks):
""" Expand a while or until block into: forever, ifelse, stopstack
- Expand a forever block to run in a separate stack
+ Expand a forever block to run in a separate stack
Parameters: the loop block, the top block, all blocks.
Return the start block of the expanded loop, and all blocks."""
@@ -911,7 +1360,7 @@ class LogoCode:
first_label_blk = HiddenBlock('string', value=action_flow_name)
# Assign new connections and build the docks
- if inflow is not None:
+ if inflow is not None and b in inflow.connections:
i = inflow.connections.index(b)
if until_blk and whileflow is not None:
inflow.connections[i] = action_first
@@ -980,14 +1429,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/tapalette.py b/TurtleArt/tapalette.py
index 6fd347a..9332155 100644
--- a/TurtleArt/tapalette.py
+++ b/TurtleArt/tapalette.py
@@ -90,7 +90,6 @@ except ImportError:
HELP_PALETTE = False
from taconstants import (EXPANDABLE_STYLE, EXPANDABLE_FLOW)
-from tautils import debug_output
from gettext import gettext as _
@@ -224,7 +223,7 @@ class Palette():
def make_palette(palette_name, colors=None, help_string=None, position=None,
- init_on_start=False):
+ init_on_start=False, translation=None):
""" Palette helper function """
if colors is None:
palette = Palette(palette_name)
diff --git a/TurtleArt/taprimitive.py b/TurtleArt/taprimitive.py
new file mode 100644
index 0000000..b689de5
--- /dev/null
+++ b/TurtleArt/taprimitive.py
@@ -0,0 +1,1162 @@
+#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
+import traceback
+
+#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 TurtleArt.tatype import (TYPE_CHAR, TYPE_INT, TYPE_FLOAT, TYPE_OBJECT,
+ TYPE_MEDIA, TYPE_COLOR, BOX_AST, ACTION_AST,
+ Type, TypeDisjunction, TATypeError, get_type,
+ TypedSubscript, TypedName, is_bound_method,
+ is_instancemethod, is_staticmethod,
+ identity, get_converter, convert, get_call_ast)
+from tautils import debug_output
+from tawindow import (TurtleArtWindow, global_objects, plugins_in_use)
+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."
+ else:
+ results, plugin = self.wants_plugin()
+ if results:
+ for k in global_objects.keys():
+ if k == plugin:
+ if k not in plugins_in_use:
+ plugins_in_use.append(k)
+ func_name = k.lower() + '.'
+ break
+
+ # 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:
+ if Primitive._DEBUG:
+ traceback.print_exc()
+ 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"]
+ else:
+ result, plugin = new_prim.wants_plugin()
+ if result:
+ first_arg = plugin
+
+ # 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
+
+ # set turtle
+ elif self == LogoCode.prim_turtle:
+ text = 'turtle = turtles.get_active_turtle()'
+ return [get_call_ast('logo.prim_turtle', new_arg_asts),
+ ast_extensions.ExtraCode(text)]
+
+ # 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.func.__name__ == '<lambda>' or 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_plugin(self):
+ """Does this Primitive want to get a plugin instance as its first
+ argument? """
+ for obj in global_objects.keys():
+ if self._wants(global_objects[obj].__class__):
+ return True, obj
+ return False, None
+
+ 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)
+ # (lambda functions are always accepted)
+ if getattr(func, '__name__', None) == '<lambda>':
+ converter = identity
+ old_type = TYPE_OBJECT
+ new_type = slot.type
+ else:
+ 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
+ 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:
+ if Primitive._DEBUG:
+ traceback.print_exc()
+ # 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:
+ if Primitive._DEBUG:
+ traceback.print_exc()
+ # 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 ac72bdb..c35125b 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,12 +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 = heading
self._heading %= 360
self._update_sprite_heading()
@@ -373,24 +368,18 @@ 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
self._turtles.turtle_window.canvas.set_fgcolor(shade=self._pen_shade,
gray=self._pen_gray,
@@ -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)
@@ -547,12 +521,7 @@ class Turtle:
def right(self, degrees, share=True):
''' Rotate turtle clockwise '''
- try:
- self._heading += degrees
- except (TypeError, ValueError):
- debug_output('bad value sent to %s' % (__name__),
- self._turtles.turtle_window.running_sugar)
- return
+ self._heading += degrees
self._heading %= 360
self._update_sprite_heading()
@@ -562,6 +531,10 @@ class Turtle:
round_int(self._heading)]))
self._turtles.turtle_window.send_event(event)
+ def left(self, degrees, share=True):
+ degrees = 0 - degrees
+ self.right(degrees, share)
+
def _draw_line(self, old, new, pendown):
if self._pen_state and pendown:
self._turtles.turtle_window.canvas.set_source_rgb()
@@ -578,13 +551,8 @@ class Turtle:
scaled_distance = distance * self._turtles.turtle_window.coord_scale
old = self.get_xy()
- try:
- xcor = old[0] + scaled_distance * sin(self._heading * DEGTOR)
- ycor = old[1] + scaled_distance * cos(self._heading * DEGTOR)
- except (TypeError, ValueError):
- debug_output('bad value sent to %s' % (__name__),
- self._turtles.turtle_window.running_sugar)
- return
+ xcor = old[0] + scaled_distance * sin(self._heading * DEGTOR)
+ ycor = old[1] + scaled_distance * cos(self._heading * DEGTOR)
self._draw_line(old, (xcor, ycor), True)
self.move_turtle((xcor, ycor))
@@ -594,19 +562,18 @@ class Turtle:
int(distance)]))
self._turtles.turtle_window.send_event(event)
+ def backward(self, distance, share=True):
+ distance = 0 - distance
+ self.forward(distance, share)
+
def set_xy(self, x, y, share=True, pendown=True, dragging=False):
old = self.get_xy()
- try:
- if dragging:
- xcor = x
- ycor = y
- else:
- xcor = x * self._turtles.turtle_window.coord_scale
- ycor = y * self._turtles.turtle_window.coord_scale
- except (TypeError, ValueError):
- debug_output('bad value sent to %s' % (__name__),
- self._turtles.turtle_window.running_sugar)
- return
+ if dragging:
+ xcor = x
+ ycor = y
+ else:
+ xcor = x * self._turtles.turtle_window.coord_scale
+ ycor = y * self._turtles.turtle_window.coord_scale
self._draw_line(old, (xcor, ycor), pendown)
self.move_turtle((xcor, ycor))
@@ -621,15 +588,10 @@ class Turtle:
''' Draw an arc '''
if self._pen_state:
self._turtles.turtle_window.canvas.set_source_rgb()
- try:
- if a < 0:
- pos = self.larc(-a, r)
- else:
- pos = self.rarc(a, r)
- except (TypeError, ValueError):
- debug_output('bad value sent to %s' % (__name__),
- self._turtles.turtle_window.running_sugar)
- return
+ if a < 0:
+ pos = self.larc(-a, r)
+ else:
+ pos = self.rarc(a, r)
self.move_turtle(pos)
@@ -736,6 +698,19 @@ class Turtle:
round_int(w)]]))
self._turtles.turtle_window.send_event(event)
+ def read_pixel(self):
+ """ Read r, g, b, a from the canvas and push b, g, r to the stack """
+ r, g, b, a = self.get_pixel()
+ self._turtles.turtle_window.lc.heap.append(b)
+ self._turtles.turtle_window.lc.heap.append(g)
+ self._turtles.turtle_window.lc.heap.append(r)
+
+ def get_color_index(self):
+ r, g, b, a = self.get_pixel()
+ color_index = self._turtles.turtle_window.canvas.get_color_index(
+ r, g, b)
+ return color_index
+
def get_name(self):
return self._name
diff --git a/TurtleArt/tatype.py b/TurtleArt/tatype.py
new file mode 100644
index 0000000..3ca47b9
--- /dev/null
+++ b/TurtleArt/tatype.py
@@ -0,0 +1,443 @@
+#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)
+
+
+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):
+ try:
+ 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)
+ except BaseException:
+ raise TATypeError(bad_value=x, bad_type=old_type,
+ req_type=new_type, message=("error during "
+ "conversion"))
+
+ 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..ff30b19 100644
--- a/TurtleArt/tautils.py
+++ b/TurtleArt/tautils.py
@@ -51,7 +51,7 @@ except (ImportError, AttributeError):
from StringIO import StringIO
from taconstants import (HIT_HIDE, HIT_SHOW, XO1, XO15, XO175, XO4, UNKNOWN,
- MAGICNUMBER, SUFFIX)
+ MAGICNUMBER, SUFFIX, ARG_MUST_BE_NUMBER)
import logging
_logger = logging.getLogger('turtleart-activity')
@@ -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]
@@ -679,14 +677,11 @@ def arithmetic_check(blk1, blk2, dock1, dock2):
return False
if dock1 == 2 and zero_arg(blk2.values[0]):
return False
- elif blk1.name \
- in ['product2', 'minus2', 'random', 'remainder2', 'string'] and \
- blk2.name \
- in ['product2', 'minus2', 'random', 'remainder2', 'string']:
+ elif blk1.name in ARG_MUST_BE_NUMBER and blk2.name in ARG_MUST_BE_NUMBER:
if blk1.name == 'string':
if not numeric_arg(blk1.values[0]):
return False
- elif blk1.name == 'string':
+ elif blk2.name == 'string':
if not numeric_arg(blk2.values[0]):
return False
elif blk1.name in ['greater2', 'less2'] and blk2.name == 'string':
@@ -808,6 +803,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 str(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 5072ab2..10867dc 100644
--- a/TurtleArt/tawindow.py
+++ b/TurtleArt/tawindow.py
@@ -58,16 +58,17 @@ 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,
+ KEY_DICT)
from tapalette import (palette_names, palette_blocks, expandable_blocks,
block_names, content_blocks, default_values,
special_names, block_styles, help_strings,
hidden_proto_blocks, string_or_number_args,
make_palette, palette_name_to_index,
- palette_init_on_start)
-from talogo import (LogoCode, primitive_dictionary, logoerror)
+ palette_init_on_start, palette_i18n_names)
+from talogo import (LogoCode, 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,10 +78,11 @@ 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)
+from tapalette import block_primitives
from sprites import (Sprites, Sprite)
if _GST_AVAILABLE:
@@ -95,6 +97,10 @@ _UNFULLSCREEN_VISIBILITY_TIMEOUT = 2
_PLUGIN_SUBPATH = 'plugins'
_MACROS_SUBPATH = 'macros'
+# the global instances of single-instance classes
+global_objects = {}
+plugins_in_use = []
+
class TurtleArtWindow():
''' TurtleArt Window class abstraction '''
@@ -168,6 +174,7 @@ class TurtleArtWindow():
self._autohide_shape = True
self.keypress = ''
self.keyvalue = 0
+ self.keyboard = 0
self._focus_out_id = None
self._insert_text_id = None
self._text_to_check = False
@@ -246,6 +253,7 @@ class TurtleArtWindow():
self.selected_selector = None
self.previous_selector = None
self.selector_shapes = []
+ self._highlighted_blk = None
self.selected_blk = None
self.selected_spr = None
self.selected_turtle = None
@@ -300,12 +308,26 @@ 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:
self._init_plugins()
self._setup_plugins()
+ def get_global_objects(self):
+ return global_objects
+
+ def get_init_complete(self):
+ if self.running_turtleart:
+ return self.activity.init_complete
+ else:
+ return True
+
def _lazy_init(self):
self._init_plugins()
self._setup_plugins()
@@ -325,8 +347,8 @@ class TurtleArtWindow():
regenerate=True,
show=True)
- if self.running_sugar:
- self.activity.check_buttons_for_fit()
+ if self.running_sugar:
+ self.activity.check_buttons_for_fit()
def _set_screen_dpi(self):
dpi = get_screen_dpi()
@@ -379,10 +401,11 @@ class TurtleArtWindow():
# Add the icon dir to the icon_theme search path
self._add_plugin_icon_dir(os.path.join(self._get_plugin_home(),
plugin_dir))
+ # Add the plugin to the list of global objects
+ global_objects[plugin_class] = self.turtleart_plugins[-1]
except Exception as e:
debug_output('Failed to load %s: %s' % (plugin_class, str(e)),
self.running_sugar)
-
def _add_plugin_icon_dir(self, dirname):
''' If there is an icon subdir, add it to the search path. '''
@@ -413,7 +436,7 @@ class TurtleArtWindow():
# If setup fails, remove the plugin from the list
self.turtleart_plugins.remove(plugin)
- def _start_plugins(self):
+ def start_plugins(self):
''' Start is called everytime we execute blocks. '''
for plugin in self.turtleart_plugins:
if hasattr(plugin, 'start'):
@@ -762,6 +785,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')
@@ -1477,11 +1503,25 @@ class TurtleArtWindow():
self.button_press(event.get_state() & gtk.gdk.CONTROL_MASK, x, y)
return True
+ def get_mouse_flag(self):
+ return self.mouse_flag
+
+ def get_mouse_button(self):
+ return self.mouse_flag == 1
+
+ def get_mouse_x(self):
+ return int(self.mouse_x - (self.canvas.width / 2))
+
+ def get_mouse_y(self):
+ return int((self.canvas.height / 2) - self.mouse_y)
+
def button_press(self, mask, x, y):
- if self.running_sugar:
- self._show_unfullscreen_button()
+ if self.running_turtleart:
+ if self.running_sugar:
+ self._show_unfullscreen_button()
- self.activity.hide_store()
+ if self.interactive_mode:
+ self.activity.hide_store()
# Find out what was clicked
spr = self.sprite_list.find_sprite((x, y))
@@ -1491,7 +1531,7 @@ class TurtleArtWindow():
blk = self.block_list.spr_to_block(spr)
if blk is not None:
# Make sure stop button is visible
- if self.running_sugar:
+ if self.running_sugar and self.running_turtleart:
self.activity.stop_turtle_button.set_icon("stopiton")
self.activity.stop_turtle_button.set_tooltip(
_('Stop turtle'))
@@ -1562,9 +1602,10 @@ before making changes to your program'))
self.status_spr.hide()
self._autohide_shape = True
- def _look_for_a_blk(self, spr, x, y):
+ def _look_for_a_blk(self, spr, x, y, blk=None):
# From the sprite at x, y, look for a corresponding block
- blk = self.block_list.spr_to_block(spr)
+ if blk is None:
+ blk = self.block_list.spr_to_block(spr)
''' If we were copying and didn't click on a block... '''
if self.copying_blocks or self.sharing_blocks or self.saving_blocks:
if blk is None or blk.type != 'block':
@@ -1586,9 +1627,9 @@ before making changes to your program'))
self._restore_from_trash(find_top_block(blk))
elif blk.type == 'proto':
if self.deleting_blocks:
- if 'my blocks' in palette_names and \
+ if 'myblocks' in palette_names and \
self.selected_palette == \
- palette_names.index('my blocks'):
+ palette_names.index('myblocks'):
self._delete_stack_alert(blk)
self.parent.get_window().set_cursor(
gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
@@ -1797,7 +1838,7 @@ before making changes to your program'))
error_output('Could not remove macro %s: %s' %
(macro_path, e))
return
- i = palette_names.index('my blocks')
+ i = palette_names.index('myblocks')
palette_blocks[i].remove(blk.name)
for pblk in self.palettes[i]:
if pblk.name == blk.name:
@@ -3166,7 +3207,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)
@@ -3584,15 +3625,67 @@ before making changes to your program'))
elif keyname == 'g':
self._align_to_grid()
- elif self.selected_blk is not None and \
- self.selected_blk.name != 'proto':
- self._process_keyboard_commands(keyname, block_flag=True)
+ elif keyname == 'Tab':
+ # For the first pass, just tab through palettes
+ if self.selected_palette is None:
+ print 'selected palette is None'
+ return True
+ else:
+ p = self.palettes[self.selected_palette]
+ i = 0
+ if self._highlighted_blk is not None:
+ self._highlighted_blk.unhighlight()
+ if self._highlighted_blk in p:
+ i = p.index(self._highlighted_blk)
+ if i == len(p) - 1:
+ i = 0
+ else:
+ i += 1
+ while(not p[i].get_visibility()):
+ i += 1
+ if i == len(p) - 1:
+ i = 0
+ if i < len(p):
+ self._highlighted_blk = p[i]
+ self._highlighted_blk.highlight()
+ self.selected_blk = p[i]
+
+ elif self.selected_blk is not None:
+ if self.selected_blk.type == 'proto':
+ if keyname == 'Return':
+ (x, y) = self.selected_blk.spr.get_xy()
+ if self.orientation == 0:
+ x += 20
+ y += PALETTE_HEIGHT + self.toolbar_offset
+ else:
+ x += PALETTE_WIDTH
+ y += 20
+ self._look_for_a_blk(None, x, y, blk=self.selected_blk)
+ self._unselect_all_blocks()
+ self.selected_spr = None
+ self.drag_group = None
+ else:
+ self._process_keyboard_commands(keyname, block_flag=True)
elif self.turtles.spr_to_turtle(self.selected_spr) is not None:
self._process_keyboard_commands(keyname, block_flag=False)
return True
+ def get_keyboard_input(self):
+ """ Query keyboard and update cached keyboard input """
+ self.window.grab_focus()
+ if len(self.keypress) == 1:
+ self.keyboard = ord(self.keypress[0])
+ elif self.keypress in KEY_DICT:
+ self.keyboard = KEY_DICT[self.keypress]
+ else:
+ self.keyboard = 0
+
+ def get_keyboard(self):
+ """ Return cached keyboard input """
+ return self.keyboard
+
def _process_keyboard_commands(self, keyname, block_flag=True):
''' Use the keyboard to move blocks and turtle '''
mov_dict = {'KP_Up': [0, 20], 'j': [0, 20], 'Up': [0, 20],
@@ -3631,7 +3724,7 @@ before making changes to your program'))
if block is not None:
self.selected_spr = block.spr
block.highlight()
- else:
+ elif blk is not None:
self._jog_block(blk, mov_dict[keyname][0],
mov_dict[keyname][1])
elif not block_flag:
@@ -4349,6 +4442,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 '''
@@ -4371,6 +4518,9 @@ before making changes to your program'))
elif shp[0] == '#':
shp = shp[1:]
label = ''
+ if self.running_sugar and \
+ shp not in ['print', 'status', 'info', 'help']:
+ self.activity.error_list.append(shp)
self.status_spr.set_shape(self.status_shapes[shp])
self.status_spr.set_label_attributes(12.0, rescale=False)
if shp == 'status':
@@ -4620,7 +4770,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,
@@ -4629,7 +4778,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'),
@@ -4649,7 +4797,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,
@@ -4658,8 +4805,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'),
@@ -4679,7 +4824,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')],
@@ -4689,52 +4833,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
@@ -4786,3 +4889,100 @@ variable'))
(b1x, b1y) = block1.spr.get_xy()
(b2x, b2y) = block2.spr.get_xy()
return ((b1x + d1x) - (b2x + d2x), (b1y + d1y) - (b2y + d2y))
+
+ def prim_load_block(self, *args):
+ ''' Load a block on to the canvas '''
+ # Place the block at the active turtle (x, y) and move the turtle
+ # into position to place the next block in the stack.
+ # TODO: Add expandable argument, media block arguments
+ name = args[0]
+ pos = self.turtles.get_active_turtle().get_xy()
+ values = []
+ for i in range(len(args) - 1):
+ values.append(args[i + 1])
+ if len(values) > 0:
+ dy = int(self._find_block(name, pos[0], pos[1], values))
+ else:
+ if name == 'delete':
+ for blk in self.just_blocks():
+ if blk.status == 'load block':
+ blk.type = 'trash'
+ blk.spr.hide()
+ dy = 0
+ else:
+ dy = int(self._find_block(name, pos[0], pos[1]))
+
+ # Reposition turtle to end of flow
+ pos = self.turtles.get_active_turtle().get_xy()
+ pos[1] -= dy
+ self.turtles.get_active_turtle().move_turtle(pos)
+
+ def _make_block(self, name, x, y, defaults):
+ if defaults is None:
+ self._new_block(name, x, y, defaults)
+ else:
+ for i, v in enumerate(defaults):
+ if isinstance(v, float) and int(v) == v:
+ defaults[i] = int(v)
+ self._new_block(name, x, y, defaults)
+
+ # Find the block we just created and attach it to a stack.
+ self.drag_group = None
+ spr = self.sprite_list.find_sprite((x, y))
+ if spr is not None:
+ blk = self.block_list.spr_to_block(spr)
+ if blk is not None:
+ self.drag_group = find_group(blk)
+ for b in self.drag_group:
+ b.status = 'load block'
+ self._snap_to_dock()
+
+ # Disassociate new block from mouse.
+ self.drag_group = None
+ return blk.docks[-1][3]
+
+ def _find_block(self, blkname, x, y, defaults=None):
+ """ Create a new block. It is a bit more work than just calling
+ _new_block(). We need to:
+ (1) translate the label name into the internal block name;
+ (2) 'dock' the block onto a stack where appropriate; and
+ (3) disassociate the new block from the mouse. """
+ x, y = self.turtles.turtle_to_screen_coordinates((x, y))
+ for name in block_names:
+ # Translate label name into block/prim name.
+ if blkname in block_names[name]: # block label is an array
+ # print 'found a match', blkname, name, block_names[name]
+ if name in content_blocks or \
+ (name in block_primitives and
+ block_primitives[name] == name):
+ # print '_make_block', blkname, name
+ return self._make_block(name, x, y, defaults)
+ elif blkname in block_names:
+ # print '_make_block', blkname
+ return self._make_block(blkname, x, y, defaults)
+ for name in special_names:
+ # Translate label name into block/prim name.
+ if blkname in special_names[name]:
+ return self._make_block(name, x, y, defaults)
+ # Check for a macro
+ if blkname in MACROS:
+ self.new_macro(blkname, x, y)
+ return 0 # Fix me: calculate flow position
+ # Block not found
+ raise logoerror("#syntaxerror")
+ return -1
+
+ def prim_load_palette(self, arg):
+ ''' Select a palette '''
+ if type(arg) in [int, float]:
+ if int(arg) < 0 or int(arg) > len(palette_names):
+ raise logoerror("#syntaxerror")
+ else:
+ self.show_toolbar_palette(int(arg))
+ else:
+ if type(arg) == unicode:
+ arg = arg.encode('utf-8')
+ if arg in palette_names or arg in palette_i18n_names:
+ self.show_toolbar_palette(palette_name_to_index(arg))
+ else:
+ raise logoerror("#syntaxerror")