Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarion Zepf <marion.zepf@gmail.com>2013-10-29 21:25:26 (GMT)
committer Walter Bender <walter@sugarlabs.org>2013-10-29 21:25:26 (GMT)
commitdb8c29ce3204b79aed7b9679c91f7abf3f6f2102 (patch)
tree287d0eed75c47669ab27ec708b0b2c24f7390d3b
parent671ee68af425063395e9f2248f93bb723b158406 (diff)
convert to type branch of python export code
-rw-r--r--NEWS1
-rw-r--r--TurtleArt/tabasics.py970
-rw-r--r--TurtleArt/tablock.py57
-rw-r--r--TurtleArt/taconstants.py9
-rw-r--r--TurtleArt/taexportpython.py93
-rw-r--r--TurtleArt/tajail.py20
-rw-r--r--TurtleArt/talogo.py260
-rw-r--r--TurtleArt/taprimitive.py1277
-rw-r--r--TurtleArt/tatype.py444
-rw-r--r--TurtleArt/tawindow.py107
-rw-r--r--doc/primitives-with-arguments.md125
-rw-r--r--doc/type-system.md116
-rw-r--r--plugins/turtle_blocks_extras/turtle_blocks_extras.py200
-rw-r--r--pyexported/window_setup.py58
-rw-r--r--util/codegen.py34
15 files changed, 2332 insertions, 1439 deletions
diff --git a/NEWS b/NEWS
index b867021..1b374d1 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,7 @@
ENHANCEMENTS:
* Add Marian Zepf's export Python
+* New translations
192
diff --git a/TurtleArt/tabasics.py b/TurtleArt/tabasics.py
index 08d0864..a80b3ae 100644
--- a/TurtleArt/tabasics.py
+++ b/TurtleArt/tabasics.py
@@ -20,67 +20,95 @@
#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 talogo import primitive_dictionary
from taconstants import (Color, CONSTANTS)
-from taprimitive import Primitive
+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 _num_type(x):
- ''' Is x a number type? '''
- if isinstance(x, (int, float)):
- return True
- return False
+from tautils import debug_output
def _millisecond():
@@ -95,14 +123,13 @@ class Palettes():
self.tw = turtle_window
self.prim_cache = {
- "check_number": Primitive(self.check_number, export_me=False),
- "convert_value_for_move": Primitive(self.convert_value_for_move,
- export_me=False),
- "convert_for_cmp": Primitive(Primitive.convert_for_cmp,
- constant_args={'decimal_point': self.tw.decimal_point}),
- "convert_to_number": Primitive(Primitive.convert_to_number,
- constant_args={'decimal_point': self.tw.decimal_point})
- } # avoid several Primitives of the same function
+ "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()
@@ -138,11 +165,10 @@ class Palettes():
logo_command='forward',
help_string=_('moves turtle forward'))
self.tw.lc.def_prim(
- 'forward',
- 1,
+ 'forward', 1,
Primitive(Turtle.forward,
- slot_wrappers={0: self.prim_cache["convert_value_for_move"]},
- call_afterwards=self.after_move))
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=self.after_move))
palette.add_block('back',
style='basic-style-1arg',
@@ -151,12 +177,12 @@ class Palettes():
default=100,
logo_command='back',
help_string=_('moves turtle backward'))
- self.tw.lc.def_prim('back', 1,
+ self.tw.lc.def_prim(
+ 'back', 1,
Primitive(Turtle.forward,
- slot_wrappers={0: Primitive(Primitive.minus,
- slot_wrappers={0: self.prim_cache["convert_value_for_move"]
- })},
- call_afterwards=self.after_move))
+ arg_descs=[ArgSlot(TYPE_NUMBER,
+ wrapper=self.prim_cache["minus"])],
+ call_afterwards=self.after_move))
palette.add_block('clean',
style='basic-style-extended-vertical',
@@ -165,15 +191,18 @@ class Palettes():
logo_command='clean',
help_string=_('clears the screen and reset the \
turtle'))
- self.tw.lc.def_prim(
- 'clean',
- 0,
- Primitive(Primitive.group, constant_args={0: [
- Primitive(self.tw.clear_plugins, call_me=False),
- Primitive(self.tw.lc.prim_clear_helper, call_me=False,
- export_me=False),
- Primitive(self.tw.canvas.clearscreen, call_me=False),
- Primitive(self.tw.turtles.reset_turtles, call_me=False)]}))
+ 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)
+ ])]))
palette.add_block('left',
style='basic-style-1arg',
@@ -186,9 +215,9 @@ in degrees)'))
self.tw.lc.def_prim(
'left', 1,
Primitive(Turtle.right,
- slot_wrappers={0: Primitive(Primitive.minus,
- slot_wrappers={0: self.prim_cache["check_number"]})},
- call_afterwards=self.after_right))
+ arg_descs=[ArgSlot(TYPE_NUMBER,
+ wrapper=self.prim_cache["minus"])],
+ call_afterwards=self.after_right))
palette.add_block('right',
style='basic-style-1arg',
@@ -199,11 +228,10 @@ in degrees)'))
help_string=_('turns turtle clockwise (angle in \
degrees)'))
self.tw.lc.def_prim(
- 'right',
- 1,
+ 'right', 1,
Primitive(Turtle.right,
- slot_wrappers={0: self.prim_cache["check_number"]},
- call_afterwards=self.after_right))
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=self.after_right))
palette.add_block('arc',
style='basic-style-2arg',
@@ -213,11 +241,10 @@ degrees)'))
logo_command='taarc',
help_string=_('moves turtle along an arc'))
self.tw.lc.def_prim(
- 'arc',
- 2,
+ 'arc', 2,
Primitive(Turtle.arc,
- slot_wrappers={0: Primitive(float, export_me=False),
- 1: Primitive(float, export_me=False)},
+ 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')
@@ -231,17 +258,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,
+ 'setxy2', 2,
Primitive(Turtle.set_xy,
- slot_wrappers={(0, 2): Primitive(Primitive.make_tuple,
- slot_wrappers={0:self.prim_cache["convert_value_for_move"],
- 1:self.prim_cache["convert_value_for_move"]
- })},
- call_afterwards=self.after_move))
+ 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'),
@@ -251,11 +273,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,
+ 'seth', 1,
Primitive(Turtle.set_heading,
- slot_wrappers={0: Primitive(float, export_me=False)},
- call_afterwards=lambda value: self.after_set('heading',value)))
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=lambda value: self.after_set(
+ 'heading', value)))
palette.add_block('xcor',
style='box-style',
@@ -266,13 +288,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,
- Primitive(Primitive.divide, constant_args={
- 0: Primitive(Turtle.get_x, constant_args={
- 0: Primitive(self.tw.turtles.get_active_turtle,
- export_me=False)}),
- 1: Primitive(self.tw.get_coord_scale)}))
+ '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',
@@ -283,13 +303,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,
- Primitive(Primitive.divide, constant_args={
- 0: Primitive(Turtle.get_y, constant_args={
- 0: Primitive(self.tw.turtles.get_active_turtle,
- export_me=False)}),
- 1: Primitive(self.tw.get_coord_scale)}))
+ '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',
@@ -299,7 +317,9 @@ 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, Primitive(Turtle.get_heading))
+ self.tw.lc.def_prim('heading', 0,
+ Primitive(
+ Turtle.get_heading, return_type=TYPE_NUMBER))
palette.add_block('turtle-label',
hidden=True,
@@ -307,7 +327,6 @@ turtle (can be used in place of a number block)'),
label=['turtle'])
# Deprecated
- primitive_dictionary['move'] = self._prim_move
palette.add_block('setxy',
hidden=True,
style='basic-style-2arg',
@@ -318,11 +337,11 @@ 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')
@@ -345,9 +364,9 @@ setxy :x :y\npendown\nend\n')
help_string=_('fills the background with (color, \
shade)'))
self.tw.lc.def_prim(
- 'fillscreen',
- 2,
- Primitive(self.tw.canvas.fillscreen))
+ 'fillscreen', 2,
+ Primitive(self.tw.canvas.fillscreen,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)]))
palette.add_block('fillscreen2',
style='basic-style-3arg',
@@ -359,9 +378,10 @@ shade)'))
help_string=_('fills the background with (color, \
shade)'))
self.tw.lc.def_prim(
- 'fillscreen2',
- 3,
- Primitive(self.tw.canvas.fillscreen_with_gray))
+ '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')
@@ -375,10 +395,12 @@ shade)'))
help_string=_('sets color of the line drawn by the \
turtle'))
self.tw.lc.def_prim(
- 'setcolor',
- 1,
+ 'setcolor', 1,
Primitive(Turtle.set_color,
- call_afterwards=lambda value: self.after_set('color', value)))
+ 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',
@@ -389,10 +411,11 @@ turtle'))
help_string=_('sets shade of the line drawn by the \
turtle'))
self.tw.lc.def_prim(
- 'setshade',
- 1,
+ 'setshade', 1,
Primitive(Turtle.set_shade,
- call_afterwards=lambda value: self.after_set('shade', value)))
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=lambda value: self.after_set(
+ 'shade', value)))
palette.add_block('setgray',
style='basic-style-1arg',
@@ -402,10 +425,11 @@ turtle'))
help_string=_('sets gray level of the line drawn by \
the turtle'))
self.tw.lc.def_prim(
- 'setgray',
- 1,
+ 'setgray', 1,
Primitive(Turtle.set_gray,
- call_afterwards=lambda value: self.after_set('gray', value)))
+ arg_descs=[ArgSlot(TYPE_NUMBER)],
+ call_afterwards=lambda value: self.after_set(
+ 'gray', value)))
palette.add_block('color',
style='box-style',
@@ -415,7 +439,9 @@ in place of a number block)'),
value_block=True,
prim_name='color',
logo_command='pencolor')
- self.tw.lc.def_prim('color', 0, Primitive(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',
@@ -433,7 +459,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, Primitive(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',
@@ -442,9 +470,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,
- Primitive(Turtle.set_pen_state, constant_args={0: False}))
+ 'penup', 0,
+ Primitive(Turtle.set_pen_state, arg_descs=[ConstantArg(False)]))
palette.add_block('pendown',
style='basic-style-extended-vertical',
@@ -453,9 +480,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,
- Primitive(Turtle.set_pen_state, constant_args={0: True}))
+ 'pendown', 0,
+ Primitive(Turtle.set_pen_state, arg_descs=[ConstantArg(True)]))
palette.add_block('penstate',
style='boolean-block-style',
@@ -463,9 +489,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,
- Primitive(Turtle.get_pen_state))
+ 'penstate', 0,
+ Primitive(Turtle.get_pen_state, return_type=TYPE_BOOL))
palette.add_block('setpensize',
style='basic-style-1arg',
@@ -478,7 +503,9 @@ turtle'))
self.tw.lc.def_prim(
'setpensize', 1,
Primitive(Turtle.set_pen_size,
- call_afterwards=lambda val: self.after_set('pensize', val)))
+ 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')
@@ -488,10 +515,7 @@ turtle'))
prim_name='startfill',
help_string=_('starts filled polygon (used with end \
fill block)'))
- self.tw.lc.def_prim(
- 'startfill',
- 0,
- Primitive(Turtle.start_fill))
+ self.tw.lc.def_prim('startfill', 0, Primitive(Turtle.start_fill))
palette.add_block('stopfill',
style='basic-style-extended-vertical',
@@ -499,10 +523,7 @@ fill block)'))
prim_name='stopfill',
help_string=_('completes filled polygon (used with \
start fill block)'))
- self.tw.lc.def_prim(
- 'stopfill',
- 0,
- Primitive(Turtle.stop_fill))
+ self.tw.lc.def_prim('stopfill', 0, Primitive(Turtle.stop_fill))
palette.add_block('pensize',
style='box-style',
@@ -513,9 +534,8 @@ in place of a number block)'),
prim_name='pensize',
logo_command='pensize')
self.tw.lc.def_prim(
- 'pensize',
- 0,
- Primitive(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')
@@ -528,18 +548,10 @@ pensize\nend\n')
colors=["#00FFFF", "#00A0A0"],
help_string=_('Palette of pen colors'))
- self._make_constant(palette, 'red', _('red'), CONSTANTS['red'])
- self._make_constant(palette, 'orange', _('orange'),
- CONSTANTS['orange'])
- self._make_constant(palette, 'yellow', _('yellow'),
- CONSTANTS['yellow'])
- self._make_constant(palette, 'green', _('green'), CONSTANTS['green'])
- self._make_constant(palette, 'cyan', _('cyan'), CONSTANTS['cyan'])
- self._make_constant(palette, 'blue', _('blue'), CONSTANTS['blue'])
- self._make_constant(palette, 'purple', _('purple'),
- CONSTANTS['purple'])
- self._make_constant(palette, 'white', _('white'), CONSTANTS['white'])
- self._make_constant(palette, 'black', _('black'), CONSTANTS['black'])
+ color_names = ('red', 'orange', 'yellow', 'green', 'cyan', 'blue',
+ 'purple', 'white', 'black')
+ for name in color_names:
+ self._make_constant(palette, name, _(name), name)
# In order to map Turtle Art colors to the standard UCB Logo palette,
# we need to define a somewhat complex set of functions.
@@ -631,10 +643,16 @@ tasetshade :shade \n')
prim_name='plus',
logo_command='sum',
help_string=_('adds two alphanumeric inputs'))
- self.tw.lc.def_prim('plus', 2,
- # TODO re-enable use with lists
- Primitive(Primitive.plus, slot_wrappers={
- (0, 2): Primitive(Primitive.convert_for_plus)}))
+ self.tw.lc.def_prim(
+ 'plus', 2,
+ # add up two numbers ...
+ or_(Primitive(Primitive.plus, return_type=TYPE_NUMBER,
+ arg_descs=[ArgSlot(TYPE_NUMBER),
+ ArgSlot(TYPE_NUMBER)]),
+ # ... or concatenate two strings
+ Primitive(Primitive.plus, return_type=TYPE_STRING,
+ arg_descs=[ArgSlot(TYPE_STRING),
+ ArgSlot(TYPE_STRING)])))
palette.add_block('minus2',
style='number-style-porch',
@@ -644,17 +662,10 @@ tasetshade :shade \n')
logo_command='taminus',
help_string=_('subtracts bottom numeric input from \
top numeric input'))
- self.tw.lc.def_prim('minus', 2,
- # TODO re-enable use with lists
- Primitive(Primitive.minus, slot_wrappers={
- 0: Primitive(self.check_number,
- export_me=False,
- slot_wrappers={
- 0: self.prim_cache["convert_to_number"]}),
- 1: Primitive(self.check_number,
- export_me=False,
- slot_wrappers={
- 0: self.prim_cache["convert_to_number"]})}))
+ self.tw.lc.def_prim(
+ 'minus', 2,
+ Primitive(Primitive.minus, return_type=TYPE_NUMBER,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)]))
define_logo_function('taminus', 'to taminus :y :x\noutput sum :x \
minus :y\nend\n')
@@ -665,17 +676,10 @@ minus :y\nend\n')
prim_name='product',
logo_command='product',
help_string=_('multiplies two numeric inputs'))
- self.tw.lc.def_prim('product', 2,
- # TODO re-enable use with lists
- Primitive(Primitive.multiply, slot_wrappers={
- 0: Primitive(self.check_number,
- export_me=False,
- slot_wrappers={
- 0: self.prim_cache["convert_to_number"]}),
- 1: Primitive(self.check_number,
- export_me=False,
- slot_wrappers={
- 0: self.prim_cache["convert_to_number"]})}))
+ self.tw.lc.def_prim(
+ 'product', 2,
+ Primitive(Primitive.multiply, return_type=TYPE_NUMBER,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)]))
palette.add_block('division2',
style='number-style-porch',
@@ -685,20 +689,10 @@ minus :y\nend\n')
logo_command='quotient',
help_string=_('divides top numeric input \
(numerator) by bottom numeric input (denominator)'))
- self.tw.lc.def_prim('division', 2,
- # TODO re-enable use with lists
- Primitive(Primitive.divide, slot_wrappers={
- 0: Primitive(self.check_number,
- export_me=False,
- slot_wrappers={
- 0: self.prim_cache["convert_to_number"]}),
- 1: Primitive(self.check_non_zero,
- export_me=False,
- slot_wrappers={
- 0: Primitive(self.check_number,
- export_me=False,
- slot_wrappers={
- 0: self.prim_cache["convert_to_number"]})})}))
+ self.tw.lc.def_prim(
+ 'division', 2,
+ Primitive(Primitive.divide, return_type=TYPE_NUMBER,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)]))
palette.add_block('identity2',
style='number-style-1arg',
@@ -707,7 +701,25 @@ minus :y\nend\n')
prim_name='id',
help_string=_('identity operator used for extending \
blocks'))
- self.tw.lc.def_prim('id', 1, Primitive(Primitive.identity))
+ 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)])))
palette.add_block('remainder2',
style='number-style-porch',
@@ -716,19 +728,10 @@ blocks'))
prim_name='remainder',
logo_command='remainder',
help_string=_('modular (remainder) operator'))
- self.tw.lc.def_prim('remainder', 2,
- Primitive(Primitive.modulo, slot_wrappers={
- 0: Primitive(self.check_number,
- export_me=False,
- slot_wrappers={
- 0: self.prim_cache["convert_to_number"]}),
- 1: Primitive(self.check_non_zero,
- export_me=False,
- slot_wrappers={
- 0: Primitive(self.check_number,
- export_me=False,
- slot_wrappers={
- 0: self.prim_cache["convert_to_number"]})})}))
+ self.tw.lc.def_prim(
+ 'remainder', 2,
+ Primitive(Primitive.modulo, return_type=TYPE_NUMBER,
+ arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)]))
palette.add_block('sqrt',
style='number-style-1arg',
@@ -737,16 +740,11 @@ blocks'))
prim_name='sqrt',
logo_command='tasqrt',
help_string=_('calculates square root'))
- self.tw.lc.def_prim('sqrt', 1,
- Primitive(sqrt,
- slot_wrappers={0: Primitive(self.check_non_negative,
- slot_wrappers={0: Primitive(self.check_number,
- slot_wrappers={0:
- self.prim_cache["convert_to_number"]},
- export_me=False)},
- export_me=False)}))
-
- primitive_dictionary['random'] = self._prim_random
+ self.tw.lc.def_prim(
+ 'sqrt', 1,
+ Primitive(Primitive.square_root, return_type=TYPE_FLOAT,
+ arg_descs=[ArgSlot(TYPE_NUMBER)]))
+
palette.add_block('random',
style='number-style-block',
label=[_('random'), _('min'), _('max')],
@@ -755,9 +753,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')
@@ -777,10 +785,17 @@ operators'))
prim_name='greater?',
logo_command='greater?',
help_string=_('logical greater-than operator'))
- self.tw.lc.def_prim('greater?', 2,
- Primitive(Primitive.greater,
- slot_wrappers={0: self.prim_cache["convert_for_cmp"],
- 1: self.prim_cache["convert_for_cmp"]}))
+ self.tw.lc.def_prim(
+ 'greater?', 2,
+ Primitive(Primitive.greater, return_type=TYPE_BOOL,
+ arg_descs=or_([ArgSlot(TYPE_COLOR),
+ ArgSlot(TYPE_COLOR)],
+ [ArgSlot(TYPE_NUMBER),
+ ArgSlot(TYPE_NUMBER)],
+ [ArgSlot(TYPE_STRING),
+ ArgSlot(TYPE_STRING)],
+ [ArgSlot(TYPE_OBJECT),
+ ArgSlot(TYPE_OBJECT)])))
palette.add_block('less2',
style='compare-porch-style',
@@ -790,10 +805,17 @@ operators'))
prim_name='less?',
logo_command='less?',
help_string=_('logical less-than operator'))
- self.tw.lc.def_prim('less?', 2,
- Primitive(Primitive.less,
- slot_wrappers={0: self.prim_cache["convert_for_cmp"],
- 1: self.prim_cache["convert_for_cmp"]}))
+ self.tw.lc.def_prim(
+ 'less?', 2,
+ Primitive(Primitive.less, return_type=TYPE_BOOL,
+ arg_descs=or_([ArgSlot(TYPE_COLOR),
+ ArgSlot(TYPE_COLOR)],
+ [ArgSlot(TYPE_NUMBER),
+ ArgSlot(TYPE_NUMBER)],
+ [ArgSlot(TYPE_STRING),
+ ArgSlot(TYPE_STRING)],
+ [ArgSlot(TYPE_OBJECT),
+ ArgSlot(TYPE_OBJECT)])))
palette.add_block('equal2',
style='compare-style',
@@ -803,10 +825,17 @@ operators'))
prim_name='equal?',
logo_command='equal?',
help_string=_('logical equal-to operator'))
- self.tw.lc.def_prim('equal?', 2,
- Primitive(Primitive.equals,
- slot_wrappers={0: self.prim_cache["convert_for_cmp"],
- 1: self.prim_cache["convert_for_cmp"]}))
+ 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',
@@ -814,7 +843,10 @@ operators'))
prim_name='not',
logo_command='not',
help_string=_('logical NOT operator'))
- self.tw.lc.def_prim('not', 1, Primitive(Primitive.not_))
+ self.tw.lc.def_prim(
+ 'not', 1,
+ Primitive(Primitive.not_, return_type=TYPE_BOOL,
+ arg_descs=[ArgSlot(TYPE_BOOL)]))
palette.add_block('and2',
style='boolean-style',
@@ -824,7 +856,9 @@ operators'))
special_name=_('and'),
help_string=_('logical AND operator'))
self.tw.lc.def_prim(
- 'and', 2, Primitive(Primitive.and_))
+ 'and', 2,
+ Primitive(Primitive.and_, return_type=TYPE_BOOL,
+ arg_descs=[ArgSlot(TYPE_BOOL), ArgSlot(TYPE_BOOL)]))
palette.add_block('or2',
style='boolean-style',
@@ -834,7 +868,9 @@ operators'))
special_name=_('or'),
help_string=_('logical OR operator'))
self.tw.lc.def_prim(
- 'or', 2, Primitive(Primitive.or_))
+ '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 '''
@@ -845,7 +881,6 @@ operators'))
colors=["#FFC000", "#A08000"],
help_string=_('Palette of flow operators'))
- primitive_dictionary['wait'] = self._prim_wait
palette.add_block('wait',
style='basic-style-1arg',
label=_('wait'),
@@ -854,9 +889,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'),
@@ -864,13 +901,13 @@ number of seconds'))
default=[None, None],
logo_command='forever',
help_string=_('loops forever'))
- self.tw.lc.def_prim('forever', 1,
- Primitive(self.tw.lc.prim_loop,
- constant_args={0: Primitive(Primitive.controller_forever,
- call_me=False)}),
+ 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'),
@@ -879,12 +916,14 @@ number of seconds'))
logo_command='repeat',
special_name=_('repeat'),
help_string=_('loops specified number of times'))
- self.tw.lc.def_prim('repeat', 2,
+ self.tw.lc.def_prim(
+ 'repeat', 2,
Primitive(self.tw.lc.prim_loop,
- slot_wrappers={0: Primitive(Primitive.controller_repeat,
- slot_wrappers={0: Primitive(self.tw.lc.int,
- slot_wrappers={0: self.prim_cache["check_number"]
- })})}),
+ arg_descs=[ArgSlot(
+ TYPE_OBJECT,
+ wrapper=Primitive(Primitive.controller_repeat,
+ arg_descs=[ArgSlot(TYPE_INT)])),
+ ArgSlot(TYPE_OBJECT, call_arg=False)]),
True)
palette.add_block('if',
@@ -896,7 +935,11 @@ number of seconds'))
logo_command='if',
help_string=_('if-then operator that uses boolean \
operators from Numbers palette'))
- self.tw.lc.def_prim('if', 2, Primitive(self.tw.lc.prim_if), True)
+ self.tw.lc.def_prim(
+ 'if', 2,
+ Primitive(self.tw.lc.prim_if,
+ arg_descs=[ArgSlot(TYPE_BOOL), ArgSlot(TYPE_OBJECT)]),
+ True)
palette.add_block('ifelse',
hidden=True, # Too big to fit palette
@@ -908,8 +951,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(self.tw.lc.prim_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',
@@ -926,7 +973,8 @@ boolean operators from Numbers palette'))
prim_name='nop',
special_name=_('horizontal space'),
help_string=_('jogs stack right'))
- self.tw.lc.def_prim('nop', 0,
+ self.tw.lc.def_prim(
+ 'nop', 0,
Primitive(Primitive.do_nothing, export_me=False))
palette.add_block('vspace',
@@ -935,18 +983,19 @@ boolean operators from Numbers palette'))
prim_name='nop',
special_name=_('vertical space'),
help_string=_('jogs stack down'))
- self.tw.lc.def_prim('nop', 0,
+ 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 '''
@@ -964,12 +1013,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,
- Primitive(Primitive.group, constant_args={0: [
- Primitive(self.tw.lc.prim_start, call_me=False,
+ 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,
- constant_args={0: 'start'}, call_me=False)]}))
+ arg_descs=[ConstantArg('start')])])]))
palette.add_block('string',
style='box-style',
@@ -986,9 +1036,14 @@ buttons'))
default=_('action'),
logo_command='to action',
help_string=_('top of nameable action stack'))
- self.tw.lc.def_prim('nop3', 1, Primitive(self.tw.lc.prim_define_stack))
+ self.tw.lc.def_prim(
+ 'nop3', 1,
+ Primitive(self.tw.lc.prim_define_stack,
+ arg_descs=[ArgSlot(TYPE_STRING)]))
- primitive_dictionary['stack'] = Primitive(self.tw.lc.prim_invoke_stack)
+ primitive_dictionary['stack'] = Primitive(
+ self.tw.lc.prim_invoke_stack,
+ arg_descs=[ArgSlot(TYPE_STRING)])
palette.add_block('stack',
style='basic-style-1arg',
label=_('action'),
@@ -997,10 +1052,8 @@ buttons'))
logo_command='action',
default=_('action'),
help_string=_('invokes named action stack'))
- self.tw.lc.def_prim('stack', 1,
- Primitive(self.tw.lc.prim_invoke_stack), True)
+ self.tw.lc.def_prim('stack', 1, primitive_dictionary['stack'], True)
- primitive_dictionary['setbox'] = Primitive(self.tw.lc.prim_set_box)
palette.add_block('storeinbox1',
hidden=True,
style='basic-style-1arg',
@@ -1010,8 +1063,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,
- Primitive(self.tw.lc.prim_set_box, constant_args={0: 'box1'}))
+ 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,
@@ -1022,8 +1077,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,
- Primitive(self.tw.lc.prim_set_box, constant_args={0: 'box2'}))
+ 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,
@@ -1033,8 +1090,10 @@ buttons'))
logo_command=':box1',
help_string=_('Variable 1 (numeric value)'),
value_block=True)
- self.tw.lc.def_prim('box1', 0,
- Primitive(self.tw.lc.prim_get_box, constant_args={0: '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,
@@ -1044,9 +1103,14 @@ buttons'))
logo_command=':box2',
help_string=_('Variable 2 (numeric value)'),
value_block=True)
- self.tw.lc.def_prim('box2', 0,
- Primitive(self.tw.lc.prim_get_box, constant_args={0: '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')],
@@ -1056,10 +1120,12 @@ buttons'))
default=[_('my box'), 100],
help_string=_('stores numeric value in named \
variable'))
- self.tw.lc.def_prim('storeinbox', 2,
- Primitive(self.tw.lc.prim_set_box))
+ self.tw.lc.def_prim('storeinbox', 2, primitive_dictionary['setbox'])
- primitive_dictionary['box'] = Primitive(self.tw.lc.prim_get_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,
@@ -1070,8 +1136,7 @@ variable'))
logo_command='box',
value_block=True,
help_string=_('named variable (numeric value)'))
- self.tw.lc.def_prim('box', 1,
- Primitive(self.tw.lc.prim_get_box))
+ self.tw.lc.def_prim('box', 1, primitive_dictionary['box'])
palette.add_block('hat1',
hidden=True,
@@ -1080,9 +1145,10 @@ variable'))
prim_name='nop1',
logo_command='to stack1\n',
help_string=_('top of Action 1 stack'))
- self.tw.lc.def_prim('nop1', 0,
+ self.tw.lc.def_prim(
+ 'nop1', 0,
Primitive(self.tw.lc.prim_define_stack,
- constant_args={0: 'stack1'}))
+ arg_descs=[ConstantArg('stack1')]))
palette.add_block('hat2',
hidden=True,
@@ -1091,9 +1157,10 @@ variable'))
prim_name='nop2',
logo_command='to stack2\n',
help_string=_('top of Action 2 stack'))
- self.tw.lc.def_prim('nop2', 0,
+ self.tw.lc.def_prim(
+ 'nop2', 0,
Primitive(self.tw.lc.prim_define_stack,
- constant_args={0: 'stack2'}))
+ arg_descs=[ConstantArg('stack2')]))
palette.add_block('stack1',
hidden=True,
@@ -1102,9 +1169,10 @@ variable'))
prim_name='stack1',
logo_command='stack1',
help_string=_('invokes Action 1 stack'))
- self.tw.lc.def_prim('stack1', 0,
+ self.tw.lc.def_prim(
+ 'stack1', 0,
Primitive(self.tw.lc.prim_invoke_stack,
- constant_args={0: 'stack1'}),
+ arg_descs=[ConstantArg('stack1')]),
True)
palette.add_block('stack2',
@@ -1114,9 +1182,10 @@ variable'))
prim_name='stack2',
logo_command='stack2',
help_string=_('invokes Action 2 stack'))
- self.tw.lc.def_prim('stack2', 0,
+ self.tw.lc.def_prim(
+ 'stack2', 0,
Primitive(self.tw.lc.prim_invoke_stack,
- constant_args={0: 'stack2'}),
+ arg_descs=[ConstantArg('stack2')]),
True)
def _trash_palette(self):
@@ -1143,15 +1212,7 @@ variable'))
label=_('clear all'),
help_string=_('move all blocks to trash'))
- # Block primitives
-
- def _prim_clear(self):
- self.tw.lc.prim_clear()
- self.tw.turtles.reset_turtles()
-
- def _prim_and(self, x, y):
- ''' Logical and '''
- return x & y
+ # Callbacks to update labels after executing a block
def after_arc(self, *ignored_args):
if self.tw.lc.update_values:
@@ -1167,57 +1228,7 @@ 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 convert_value_for_move(self, value):
- ''' Perform type conversion and other preprocessing on the parameter,
- so it can be passed to the 'move' primitive. '''
- if value is None:
- return value
-
- def _convert_to_float(val):
- if not _num_type(val):
- raise logoerror("#notanumber")
- return float(val)
-
- if isinstance(value, (tuple, list)):
- (val1, val2) = value
- val1_float = _convert_to_float(val1)
- val2_float = _convert_to_float(val2)
- value_converted = (val1_float, val2_float)
- else:
- value_converted = _convert_to_float(value)
- return value_converted
-
- def _prim_move(self, cmd, value1, pendown=True,
- reverse=False):
- ''' Turtle moves by method specified in value1 '''
-
- value1_conv = self.convert_value_for_move(value1)
-
- cmd(value1_conv, pendown=pendown)
-
- self.after_move()
-
- def after_move(self, *ignored_args):
+ 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(
@@ -1229,279 +1240,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 check_number(self, value):
- ''' Check if value is a number. If yes, return the value. If no,
- raise a logoerror. '''
- if not _num_type(value):
- raise logoerror("#notanumber")
- return value
-
- def check_non_negative(self, x, msg="#negroot"):
- ''' Raise a logoerror iff x is negative. Otherwise, return x
- unchanged.
- msg -- the name of the logoerror message '''
- if x < 0:
- raise logoerror(msg)
- return x
-
- def check_non_zero(self, x, msg="#zerodivide"):
- ''' Raise a logoerror iff x is zero. Otherwise, return x
- unchanged.
- msg -- the name of the logoerror message '''
- if x == 0:
- raise logoerror(msg)
- return x
-
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 '''
- if value is not None:
- cmd(value)
- if self.tw.lc.update_values:
- self.tw.lc.update_label_value(name, value)
-
def after_set(self, name, value=None):
''' Update the associated value blocks '''
if value is not None:
if self.tw.lc.update_values:
self.tw.lc.update_label_value(name, value)
- def _prim_setbox(self, name, x, val):
- ''' Define value of named box '''
- if x is not None:
- 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_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 primitives
-
- 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_plus(self, x, y):
- ''' Add numbers, concat strings '''
- if isinstance(x, Color):
- x = int(x)
- if isinstance(y, Color):
- y = int(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_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")
-
# Utilities
- def _string_to_num(self, x):
- ''' Try to convert 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 isinstance(x, Color):
- return int(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 '''
+ constant = CONSTANTS[constant_key]
if isinstance(constant, Color):
if constant.color is not None:
- value = str(constant.color)
+ logo_command = str(constant.color)
else:
# Black or White
- value = '0 tasetshade %d' % (constant.shade)
+ 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)
+ logo_command=logo_command)
self.tw.lc.def_prim(block_name, 0,
- Primitive(Primitive.identity, constant_args={0: constant}))
+ Primitive(CONSTANTS.get, return_type=return_type,
+ arg_descs=[ConstantArg(constant_key)]))
diff --git a/TurtleArt/tablock.py b/TurtleArt/tablock.py
index f8826ff..b39ceaa 100644
--- a/TurtleArt/tablock.py
+++ b/TurtleArt/tablock.py
@@ -25,7 +25,7 @@ import cairo
from taconstants import (EXPANDABLE, EXPANDABLE_ARGS, OLD_NAMES, CONSTANTS,
STANDARD_STROKE_WIDTH, BLOCK_SCALE, BOX_COLORS,
GRADIENT_COLOR, EXPANDABLE_FLOW, Color,
- PREFIX_DICTIONARY)
+ MEDIA_BLOCK2TYPE)
from tapalette import (palette_blocks, block_colors, expandable_blocks,
content_blocks, block_names, block_primitives,
block_styles, special_block_colors)
@@ -37,6 +37,33 @@ 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:
@@ -245,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
@@ -294,7 +328,6 @@ class Block:
if not self.is_value_block():
return None
- result = ''
if self.name == 'number':
try:
return float(self.values[0])
@@ -304,23 +337,21 @@ class Block:
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]
- elif self.name in PREFIX_DICTIONARY:
- if add_type_prefix:
- result = PREFIX_DICTIONARY[self.name]
- result += str(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:
- if add_type_prefix:
- result = '#smedia_'
- result += self.name.upper()
+ return Media('media', self.name.upper())
else:
return None
- return result
def highlight(self):
""" We may want to highlight a block... """
@@ -598,7 +629,7 @@ class Block:
if self.spr is None:
return
if isinstance(self.name, unicode):
- self.name = self.name.encode('utf8')
+ self.name = self.name.encode('utf-8')
if self.name in content_blocks:
n = len(self.values)
if n == 0:
@@ -679,7 +710,7 @@ class Block:
self.svg.set_stroke_width(STANDARD_STROKE_WIDTH)
self.svg.clear_docks()
if isinstance(self.name, unicode):
- self.name = self.name.encode('utf8')
+ self.name = self.name.encode('utf-8')
for k in block_styles.keys():
if self.name in block_styles[k]:
if isinstance(self.block_methods[k], list):
@@ -1016,7 +1047,7 @@ class Block:
self._make_block_graphics(svg, self.svg.basic_block)
self.docks = [['flow', True, self.svg.docks[0][0],
self.svg.docks[0][1]],
- ['unavailable', True, 0, self.svg.docks[0][1] + 10, '['],
+ ['flow', True, 0, self.svg.docks[0][1] + 10, '['],
['flow', False, self.svg.docks[1][0],
self.svg.docks[1][1], ']']]
diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py
index ba0b5a7..9666573 100644
--- a/TurtleArt/taconstants.py
+++ b/TurtleArt/taconstants.py
@@ -103,6 +103,9 @@ class Color(object):
def __float__(self):
return float(int(self))
+ def get_number_string(self):
+ return str(int(self))
+
def __str__(self):
return str(self.name)
@@ -194,10 +197,10 @@ OLD_DOCK = ['and', 'or', 'plus', 'minus', 'division', 'product', 'remainder']
CONTENT_ARGS = ['show', 'showaligned', 'push', 'storein', 'storeinbox1',
'storeinbox2']
-PREFIX_DICTIONARY = {}
+MEDIA_BLOCK2TYPE = {} # map media blocks to media types
+
-# These blocks get a special skin
-BLOCKS_WITH_SKIN = []
+BLOCKS_WITH_SKIN = [] # These blocks get a special skin
PYTHON_SKIN = []
diff --git a/TurtleArt/taexportpython.py b/TurtleArt/taexportpython.py
index 88a73a0..76087ad 100644
--- a/TurtleArt/taexportpython.py
+++ b/TurtleArt/taexportpython.py
@@ -30,7 +30,8 @@ import util.codegen as codegen
#from ast_pprint import * # only used for debugging, safe to comment out
from talogo import LogoCode
-from taprimitive import (Primitive, PyExportError, value_to_ast)
+from taprimitive import (ast_yield_true, Primitive, PyExportError,
+ value_to_ast)
from tautils import (debug_output, find_group, find_top_block, get_stack_name)
@@ -38,8 +39,9 @@ from tautils import (debug_output, find_group, find_top_block, get_stack_name)
_SETUP_CODE_START = """\
#!/usr/bin/env python
-from math import sqrt
+from time import *
from random import uniform
+from math import *
from pyexported.window_setup import *
@@ -50,21 +52,22 @@ BOX = {}
ACTION = {}
-
"""
_SETUP_CODE_END = """\
-
-
if __name__ == '__main__':
+ tw.lc.start_time = time()
tw.lc.icall(start)
gobject.idle_add(tw.lc.doevalstep)
gtk.main()
-
-
"""
_ACTION_STACK_START = """\
def %s():
+"""
+_START_STACK_START_ADD = """\
+ tw.start_plugins()
+"""
+_ACTION_STACK_PREAMBLE = """\
turtle = tw.turtles.get_active_turtle()
turtles = tw.turtles
canvas = tw.canvas
@@ -104,9 +107,15 @@ def save_python(tw):
def _action_stack_to_python(block, lc, name="start"):
""" Turn a stack of blocks into python code
name -- the name of the action stack (defaults to "start") """
+ if isinstance(name, int):
+ name = float(name)
+ if not isinstance(name, basestring):
+ name = str(name)
+
# traverse the block stack and get the AST for every block
ast_list = _walk_action_stack(block, lc)
- ast_list.append(_ast_yield_true())
+ if not isinstance(ast_list[-1], ast.Yield):
+ ast_list.append(ast_yield_true())
action_stack_ast = ast.Module(body=ast_list)
#debug_output(str(action_stack_ast))
@@ -115,29 +124,44 @@ def _action_stack_to_python(block, lc, name="start"):
# wrap the action stack setup code around everything
name_id = _make_identifier(name)
+ if name == 'start':
+ pre_preamble = _START_STACK_START_ADD
+ else:
+ pre_preamble = ''
generated_code = _indent(generated_code, 1)
if generated_code.endswith(linesep):
newline = ""
else:
newline = linesep
snippets = [_ACTION_STACK_START % (name_id),
+ pre_preamble,
+ _ACTION_STACK_PREAMBLE,
generated_code,
newline,
_ACTION_STACK_END % (name, name_id)]
return "".join(snippets)
-def _walk_action_stack(top_block, lc):
- """ Turn a stack of blocks into a list of ASTs """
+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)
- value_ast = value_to_ast(raw_value)
- if value_ast is not None:
- return [value_ast]
+ if convert_me:
+ value_ast = value_to_ast(raw_value)
+ if value_ast is not None:
+ return [value_ast]
+ else:
+ return []
else:
- return []
+ if raw_value is not None:
+ return [raw_value]
+ else:
+ return []
def _get_prim(block):
prim = lc.get_prim_callable(block.primitive)
@@ -156,20 +180,23 @@ def _walk_action_stack(top_block, lc):
PyExportError on failure. """
if prim is None:
prim = _get_prim(block)
- if prim.export_me:
- try:
- new_ast = prim.get_ast(*arg_asts)
- except ValueError:
- traceback.print_exc()
- raise PyExportError(_("error while exporting block"),
- block=block)
- if isinstance(new_ast, (list, tuple)):
- ast_list.extend(new_ast)
- elif new_ast is not None:
+ 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)
- elif arg_asts:
- 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
@@ -192,14 +219,17 @@ def _walk_action_stack(top_block, lc):
else:
# embedded stack of blocks (body of conditional or loop) or
# argument block
- new_arg_asts = _walk_action_stack(conn, lc)
if dock[0] == 'flow':
# body of conditional or loop
- if prim == LogoCode.prim_loop:
- new_arg_asts.append(_ast_yield_true())
+ 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
@@ -226,7 +256,4 @@ def _indent(code, num_levels=1):
new_line_list.append(indentation + line)
return linesep.join(new_line_list)
-def _ast_yield_true():
- return ast.Yield(value=ast.Name(id='True', ctx=ast.Load))
-
diff --git a/TurtleArt/tajail.py b/TurtleArt/tajail.py
index 40517cd..539c126 100644
--- a/TurtleArt/tajail.py
+++ b/TurtleArt/tajail.py
@@ -27,21 +27,11 @@ from math import *
def myfunc(f, args):
''' Run inline Python code '''
# check to make sure no import calls are made
- if len(args) == 1:
- myf = 'def f(x): return ' + f.replace('import', '')
- userdefined = {}
- exec myf in globals(), userdefined
- return userdefined.values()[0](args[0])
- elif len(args) == 2:
- myf = 'def f(x, y): return ' + f.replace('import', '')
- userdefined = {}
- exec myf in globals(), userdefined
- return userdefined.values()[0](args[0], args[1])
- elif len(args) == 3:
- myf = 'def f(x, y, z): return ' + f.replace('import', '')
- userdefined = {}
- exec myf in globals(), userdefined
- return userdefined.values()[0](args[0], args[1], args[2])
+ params = ", ".join(['x', 'y', 'z'][:len(args)])
+ myf = ''.join(['def f(', params, '): return ', f.replace('import', '')])
+ userdefined = {}
+ exec myf in globals(), userdefined
+ return userdefined.values()[0](*args)
def myfunc_import(parent, f, x):
diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py
index ef482a1..25d316c 100644
--- a/TurtleArt/talogo.py
+++ b/TurtleArt/talogo.py
@@ -25,6 +25,7 @@ import gtk
from time import time, sleep
from operator import isNumberType
+from os.path import exists as os_path_exists
from UserDict import UserDict
try:
@@ -35,12 +36,13 @@ except ImportError:
import traceback
-from tablock import (Block, media_blocks_dictionary)
+from tablock import (Block, Media, media_blocks_dictionary)
from taconstants import (TAB_LAYER, DEFAULT_SCALE)
+from tajail import myfunc
from tapalette import (block_names, value_blocks)
-from tautils import (get_pixbuf_from_journal, convert, data_from_file,
- text_media_type, round_int, debug_output, find_group,
- get_stack_name)
+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)
try:
from util.RtfParser import RtfTextOnly
@@ -82,6 +84,20 @@ class logoerror(Exception):
return str(self.value)
+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):
@@ -193,7 +209,11 @@ class LogoCode:
def get_prim_callable(self, name):
""" Return the callable primitive associated with the given name """
- return self.oblist[name].fcn
+ 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().
@@ -227,6 +247,7 @@ class LogoCode:
for b in blocks:
b.unhighlight()
+ '''
# Hidden macro expansions
for b in blocks:
if b.name in ['while', 'until']:
@@ -240,6 +261,7 @@ class LogoCode:
blocks = new_blocks[:]
if b == blk:
blk = action_blk
+ '''
for b in blocks:
if b.name in ('hat', 'hat1', 'hat2'):
@@ -276,7 +298,8 @@ class LogoCode:
return ['%nothing%', '%nothing%']
code = []
dock = blk.docks[0]
- if len(dock) > 4 and dock[4] in ('[', ']', ']['): # 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:
@@ -296,7 +319,8 @@ class LogoCode:
for i in range(1, len(blk.connections)):
b = blk.connections[i]
dock = blk.docks[i]
- if len(dock) > 4 and dock[4] in ('[', ']', ']['): # 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:
@@ -326,7 +350,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))
@@ -374,6 +400,7 @@ class LogoCode:
if self._disable_help:
self.tw.no_help = False
self._disable_help = False
+ self.tw.display_coordinates()
def icall(self, fcn, *args):
""" Add a function and its arguments to the program stack. """
@@ -468,7 +495,10 @@ class LogoCode:
self.tw.showblocks()
self.tw.display_coordinates()
raise logoerror("#noinput")
- call_args = type(self.cfun.fcn).__name__ != 'Primitive'
+ 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, call_args)
@@ -520,21 +550,48 @@ 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, ve:
- if self.tw.running_turtleart:
- debug_output('generator already executing',
- self.tw.running_sugar)
- self.tw.running_blocks = False
- else:
- traceback.print_exc()
- self.tw.showlabel('status', 'ValueError: ' +
- str(ve))
+ debug_output('generator already executing',
+ self.tw.running_sugar)
+ self.tw.running_blocks = False
return False
+ except TATypeError as tte:
+ # TODO insert the correct block name
+ # (self.cfun.name is only the name of the
+ # outermost block in this statement/ line of code)
+ # use logoerror("#notanumber") when possible
+ if (tte.req_type in TYPES_NUMERIC and
+ tte.bad_type not in TYPES_NUMERIC):
+ raise logoerror("#notanumber")
+ else:
+ raise logoerror(
+ "%s %s %s %s" %
+ (self.cfun.name, _("doesn't like"),
+ str(tte.bad_value), _("as input")))
+ except ZeroDivisionError:
+ raise logoerror("#zerodivide")
+ except NegativeRootError:
+ raise logoerror("#negroot")
+ except IndexError:
+ raise logoerror("#emptyheap")
else:
- return False
+ try:
+ self.step.next()
+ except BaseException as error:
+ if isinstance(error, (StopIteration,
+ logoerror)):
+ raise error
+ else:
+ traceback.print_exc()
+ self.tw.showlabel(
+ 'status', '%s: %s' %
+ (type(error).__name__, str(error)))
+ return False
except StopIteration:
if self.tw.running_turtleart:
# self.tw.turtles.show_all()
@@ -611,18 +668,30 @@ class LogoCode:
def prim_clear(self):
""" Clear screen """
self.tw.clear_plugins()
- self.prim_clear_helper()
+ 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 prim_clear_helper(self):
+ def stop_playing_media(self):
if self.tw.gst_available:
from tagplay import stop_media
stop_media(self)
+
+ def reset_scale(self):
self.scale = DEFAULT_SCALE
- self.hidden_turtle = None
+
+ def reset_timer(self):
self.start_time = time()
- self.clear_value_blocks()
+
+ 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()
@@ -645,6 +714,29 @@ class LogoCode:
self.ireturn()
yield True
+ def prim_clamp(self, blklist):
+ """ Run clamp blklist """
+ self.icall(self.evline, blklist[:])
+ yield True
+ self.procstop = False
+ self.ireturn()
+ yield True
+
+ def prim_stop_stack(self):
+ """ Stop execution of a stack """
+ self.procstop = True
+
+ def prim_wait(self, wait_time):
+ """ Show the turtle while we wait """
+ self.tw.turtles.get_active_turtle().show()
+ endtime = _millisecond() + wait_time * 1000.
+ while _millisecond() < endtime:
+ sleep(wait_time / 10.)
+ yield True
+ self.tw.turtles.get_active_turtle().hide()
+ self.ireturn()
+ yield True
+
def prim_if(self, boolean, blklist):
""" If bool, do list """
if boolean:
@@ -679,6 +771,7 @@ class LogoCode:
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):
@@ -688,9 +781,12 @@ class LogoCode:
return (name, True)
else:
# make sure '5' and '5.0' point to the same box
- if isinstance(name, (int, long)):
- name = float(name)
- return ('box3' + str(name), False)
+ 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 """
@@ -717,6 +813,47 @@ class LogoCode:
name = float(name)
return 'stack3' + str(name)
+ def get_heap(self):
+ return self.heap
+
+ def reset_heap(self):
+ """ Reset heap to an empty list """
+ # empty the list rather than setting it to a new empty list object,
+ # so the object references are preserved
+ while self.heap:
+ self.heap.pop()
+
+ def prim_myfunction(self, f, *args):
+ """ Programmable block (Call tajail.myfunc and convert any errors to
+ logoerrors) """
+ try:
+ y = myfunc(f, args)
+ if str(y) == 'nan':
+ debug_output('Python function returned NAN',
+ self.tw.running_sugar)
+ self.stop_logo()
+ raise logoerror("#notanumber")
+ else:
+ return y
+ except ZeroDivisionError:
+ self.stop_logo()
+ raise logoerror("#zerodivide")
+ except ValueError, e:
+ self.stop_logo()
+ raise logoerror('#' + str(e))
+ except SyntaxError, e:
+ self.stop_logo()
+ raise logoerror('#' + str(e))
+ except NameError, e:
+ self.stop_logo()
+ raise logoerror('#' + str(e))
+ except OverflowError:
+ self.stop_logo()
+ raise logoerror("#overflowerror")
+ except TypeError:
+ self.stop_logo()
+ raise logoerror("#notanumber")
+
def clear_value_blocks(self):
if not hasattr(self, 'value_blocks_to_update'):
return
@@ -792,6 +929,75 @@ class LogoCode:
for blk in drag_group:
blk.spr.move_relative((dx, 0))
+ def show(self, obj, center=False):
+ """ Show is the general-purpose media-rendering block. """
+ # media
+ if isinstance(obj, Media) and obj.value:
+ self.filepath = None
+ self.pixbuf = None # Camera writes directly to pixbuf
+ self.dsobject = None
+
+ # camera snapshot
+ if obj.value.lower() in media_blocks_dictionary:
+ media_blocks_dictionary[obj.value.lower()]()
+ # file path
+ elif os_path_exists(obj.value):
+ self.filepath = obj.value
+ # datastore object
+ elif self.tw.running_sugar:
+ from sugar.datastore import datastore
+ try:
+ self.dsobject = datastore.get(obj.value)
+ except:
+ debug_output("Couldn't find dsobject %s" %
+ (obj.value), self.tw.running_sugar)
+ if self.dsobject is not None:
+ self.filepath = self.dsobject.file_path
+
+ if self.pixbuf is not None:
+ self.insert_image(center=center, pixbuf=True)
+ elif self.filepath is None:
+ if self.dsobject is not None:
+ self.tw.showlabel(
+ 'nojournal',
+ self.dsobject.metadata['title'])
+ else:
+ self.tw.showlabel('nojournal', obj.value)
+ debug_output("Couldn't open %s" % (obj.value),
+ self.tw.running_sugar)
+ elif obj.type == 'media':
+ self.insert_image(center=center)
+ elif obj.type == 'descr':
+ mimetype = None
+ if self.dsobject is not None and \
+ 'mime_type' in self.dsobject.metadata:
+ mimetype = self.dsobject.metadata['mime_type']
+ description = None
+ if self.dsobject is not None and \
+ 'description' in self.dsobject.metadata:
+ description = self.dsobject.metadata[
+ 'description']
+ self.insert_desc(mimetype, description)
+ elif obj.type == 'audio':
+ self.play_sound()
+ elif obj.type == 'video':
+ self.play_video()
+
+ if self.dsobject is not None:
+ self.dsobject.destroy()
+
+ # text or number
+ elif isinstance(obj, (basestring, float, int)):
+ if isinstance(obj, (float, int)):
+ obj = round_int(obj)
+ x, y = self.x2tx(), self.y2ty()
+ if center:
+ y -= self.tw.canvas.textsize
+ self.tw.turtles.get_active_turtle().draw_text(
+ obj, x, y,
+ int(self.tw.canvas.textsize * self.scale / 100.),
+ self.tw.canvas.width - x)
+
def push_file_data_to_heap(self, dsobject):
""" push contents of a data store object (assuming json encoding) """
data = data_from_file(dsobject.file_path)
@@ -967,7 +1173,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."""
diff --git a/TurtleArt/taprimitive.py b/TurtleArt/taprimitive.py
index d4fd5d5..112540a 100644
--- a/TurtleArt/taprimitive.py
+++ b/TurtleArt/taprimitive.py
@@ -20,14 +20,21 @@
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
+from talogo import (LogoCode, logoerror, NegativeRootError)
from taturtle import (Turtle, Turtles)
+from tatype import *
+from tautils import debug_output
from tawindow import (global_objects, TurtleArtWindow)
+from util import ast_extensions
class PyExportError(BaseException):
@@ -48,19 +55,18 @@ class PyExportError(BaseException):
class Primitive(object):
- """ Something that can be called when the block code is executed in TA,
+ """ 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,
- 'integer_division': ast.FloorDiv,
- 'bitwise_and': ast.BitAnd,
- 'bitwise_or': ast.BitOr,
'and_': ast.And,
'or_': ast.Or,
'not_': ast.Not,
@@ -68,261 +74,253 @@ class Primitive(object):
'less': ast.Lt,
'greater': ast.Gt}
- def __init__(self, func, constant_args=None, slot_wrappers=None,
- call_afterwards=None, call_me=True, export_me=True):
- """ constant_args -- A dictionary containing constant arguments to be
- passed to the function. It uses the same key scheme as
- slot_wrappers, except that argument ranges are not supported.
- The constant args and kwargs are added to the runtime args and
- kwargs before the slot wrappers are called.
- slot_wrappers -- A dictionary mapping from the index of an
- argument in the args list to another Primitive that should be
- wrapped around the actual argument value (e.g., to convert a
- positive number to a negative one). For keyword arguments, the
- key in slot_wrappers should be the same as the kwargs key. To pass
- multiple arguments to the slot wrapper, use a tuple of the first
- and last argument number (the latter increased by 1) as a key.
- Negative argument indices are not supported.
+ 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)
- call_me -- True if this Primitive should be called (default), False
- if it should be passed on as a Primitive object
export_me -- True iff this Primitive should be exported to Python
code (the default case) """
self.func = func
+ self.return_type = return_type
- if constant_args is None:
- self.constant_args = {}
+ if arg_descs is None:
+ self.arg_descs = []
else:
- self.constant_args = constant_args
+ self.arg_descs = arg_descs
- if slot_wrappers is None:
- self.slot_wrappers = {}
+ if kwarg_descs is None:
+ self.kwarg_descs = {}
else:
- # check for duplicate argument indices
- msg = ("argument at index %d is associated with multiple slot "
- "wrappers")
- nums = set()
- tuples = []
- for k in slot_wrappers.keys():
- if isinstance(k, int):
- nums.add(k)
- elif isinstance(k, tuple):
- tuples.append(k)
- tuples.sort()
- prev_tuple = (0, 0)
- for tuple_ in tuples:
- if prev_tuple[1] > tuple_[0]:
- raise KeyError(msg % (tuple_[0]))
- for i in range(*tuple_):
- if i in nums:
- raise KeyError(msg % (i))
- prev_tuple = tuple_
- self.slot_wrappers = slot_wrappers
+ self.kwarg_descs = kwarg_descs
self.call_afterwards = call_afterwards
- self.call_me = call_me
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(" + repr(self.func) + ")"
-
- def _apply_wrappers(self, runtime_args, runtime_kwargs,
- convert_to_ast=False):
- """ Apply the slot wrappers """
- # make a map from the start indices of all ranges to their ends
- range_ends = {}
- for range_tuple in sorted(self.slot_wrappers.keys()):
- if isinstance(range_tuple, tuple):
- (start, end) = range_tuple
- range_ends[start] = end
+ return "Primitive(%s -> %s)" % (repr(self.func), str(self.return_type))
- new_args = []
- i = 0
- while i < len(runtime_args):
- arg = runtime_args[i]
- wrapper = self.slot_wrappers.get(i)
- if wrapper is None:
- (start, end) = (i, range_ends.get(i))
- if end is None:
- # no slot wrapper found
- # convert to AST, but don't call
- if convert_to_ast and isinstance(arg, Primitive):
- new_args.append(arg.get_ast())
- else:
- new_args.append(arg)
- i += 1
- else:
- # range -> slot wrapper around a range of arguments
- wrapper = self.slot_wrappers.get((start, end))
- args_for_wrapper = runtime_args[start:end]
- if not convert_to_ast and call_me(wrapper):
- wrapper_output = wrapper(*args_for_wrapper)
- elif convert_to_ast and export_me(wrapper):
- wrapper_output = value_to_ast(wrapper,
- *args_for_wrapper)
+ @property
+ def __name__(self):
+ return self.func.__name__
+
+ def get_name_for_export(self):
+ """ Return the expression (as a string) that represents this Primitive
+ in the exported Python code, e.g., 'turtle.forward'. """
+ func_name = ""
+ if self.wants_turtle():
+ func_name = "turtle."
+ elif self.wants_turtles():
+ func_name = "turtles."
+ elif self.wants_canvas():
+ func_name = "canvas."
+ elif self.wants_logocode():
+ func_name = "logo."
+ elif self.wants_heap():
+ func_name = "logo.heap."
+ elif self.wants_tawindow():
+ func_name = "tw."
+ # get the name of the function directly from the function itself
+ func_name += self.func.__name__
+ return func_name
+
+ def are_slots_filled(self):
+ """ Return True iff none of the arg_descs or kwarg_descs is an
+ ArgSlot. """
+ for arg_desc in self.arg_descs:
+ if isinstance(arg_desc, ArgSlot):
+ return False
+ for key in self.kwarg_descs:
+ if isinstance(self.kwarg_descs[key], ArgSlot):
+ return False
+ return True
+
+ def fill_slots(self, arguments=None, keywords=None, convert_to_ast=False,
+ call_my_args=True):
+ """ Return a copy of this Primitive whose ArgSlots are filled with
+ the given arguments, turned into ConstantArgs. Call the arguments,
+ apply their wrappers, and check their types as appropriate. """
+ if arguments is None:
+ arguments = []
+ if keywords is None:
+ keywords = {}
+
+ new_prim = self.copy()
+
+ if isinstance(new_prim.arg_descs, ArgListDisjunction):
+ slot_list_alternatives = list(new_prim.arg_descs)
+ else:
+ slot_list_alternatives = [new_prim.arg_descs]
+
+ # arguments
+ error = None
+ filler = None
+ for slot_list in slot_list_alternatives:
+ error = None
+ new_slot_list = []
+ filler_list = list(arguments[:])
+ for slot in slot_list:
+ if isinstance(slot, ArgSlot):
+ filler = filler_list.pop(0)
+ try:
+ const = slot.fill(filler,
+ convert_to_ast=convert_to_ast,
+ call_my_args=call_my_args)
+ except TATypeError as error:
+ if Primitive._DEBUG:
+ traceback.print_exc()
+ break
else:
- # apply all contained wrappers, but skip this one
- (all_args, unused) = wrapper._add_constant_args(
- args_for_wrapper, runtime_kwargs={},
- convert_to_ast=convert_to_ast)
- (my_new_args, unused) = wrapper._apply_wrappers(
- all_args, runtime_kwargs={},
- convert_to_ast=convert_to_ast)
- wrapper_output = my_new_args
- new_args.append(wrapper_output)
- i += end - start
- else:
- # number -> slot wrapper around one argument
- if not convert_to_ast and call_me(wrapper):
- new_arg = wrapper(arg)
- elif convert_to_ast and export_me(wrapper):
- new_arg = value_to_ast(wrapper, arg)
+ new_slot_list.append(const)
else:
- # apply all contained wrappers, but skip this one
- (all_args, unused) = wrapper._add_constant_args(
- [arg],
- runtime_kwargs={}, convert_to_ast=convert_to_ast)
- (my_new_args, unused) = wrapper._apply_wrappers(
- all_args,
- runtime_kwargs={}, convert_to_ast=convert_to_ast)
- new_arg = my_new_args[0]
- new_args.append(new_arg)
- i += 1
-
+ 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, value) in runtime_kwargs.iteritems():
- wrapper = self.slot_wrappers.get(key)
- if wrapper is not None:
- if not convert_to_ast and call_me(wrapper):
- new_value = wrapper(value)
- elif convert_to_ast and export_me(wrapper):
- new_value = value_to_ast(wrapper, value)
- else:
- # apply all contained wrappers, but skip this one
- (unused, all_kwargs) = wrapper._add_constant_args(
- [],
- runtime_kwargs={key: value},
- convert_to_ast=convert_to_ast)
- (unused, my_new_kwargs) = wrapper._apply_wrappers(
- [],
- runtime_kwargs={key: all_kwargs[key]},
- convert_to_ast=convert_to_ast)
- new_value = my_new_kwargs[key]
- new_kwargs[key] = new_value
- else:
- new_kwargs[key] = value
-
+ 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 _add_constant_args(self, runtime_args, runtime_kwargs,
- convert_to_ast=False):
- """ Add the constant args and kwargs to the given runtime args and
- kwargs. Return a list containing all args and a dictionary with all
- kwargs.
- convert_to_ast -- convert all constant arguments to ASTs? """
- all_args = []
- all_kwargs = runtime_kwargs.copy()
-
- # args
- i = 0
-
- def _insert_c_args(i):
- while i in self.constant_args:
- c_arg = self.constant_args[i]
- if not convert_to_ast and call_me(c_arg):
- all_args.append(c_arg())
- elif convert_to_ast:
- if export_me(c_arg):
- all_args.append(value_to_ast(c_arg))
- else:
- all_args.append(c_arg)
- i += 1
- return i
- for arg in runtime_args:
- i = _insert_c_args(i)
- all_args.append(arg)
- i += 1
- i = _insert_c_args(i)
-
- # kwargs
- for (key, value) in self.constant_args.iteritems():
- if isinstance(key, basestring):
- if not convert_to_ast and call_me(value):
- all_kwargs[key] = value()
- elif convert_to_ast:
- if export_me(value):
- all_kwargs[key] = value_to_ast(value)
- else:
- all_kwargs[key] = value
-
- return (all_args, all_kwargs)
+ def 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
+ """ Execute the function, passing it the arguments received at
runtime. Also call the function in self.call_afterwards and pass it
all runtime_args and runtime_kwargs.
- If the very first argument is a LogoCode instance, it may be
- replaced with the active turtle, the canvas, or nothing (depending
- on what this primitive wants as its first arg). This argument is
- also exempt from the slot wrappers. """
+ 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:]
- runtime_args_copy = runtime_args[:]
- runtime_args = []
- for arg in runtime_args_copy:
- if isinstance(arg, tuple) and arg and callable(arg[0]):
- runtime_args.append(arg[0](*arg[1:]))
- else:
- runtime_args.append(arg)
+ 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?
- if self.wants_turtle():
- first_arg = global_objects["turtles"].get_active_turtle()
- elif self.wants_turtles():
- first_arg = global_objects["turtles"]
- elif self.wants_canvas():
- first_arg = global_objects["canvas"]
- elif self.wants_logocode():
- first_arg = global_objects["logo"]
- elif self.wants_tawindow():
- first_arg = global_objects["window"]
- else:
- first_arg = None
-
- # constant arguments
- (all_args, all_kwargs) = self._add_constant_args(runtime_args,
- runtime_kwargs)
-
- # slot wrappers
- (new_args, new_kwargs) = self._apply_wrappers(all_args, all_kwargs)
+ first_arg = None
+ if not is_bound_method(new_prim.func):
+ if new_prim.wants_turtle():
+ first_arg = global_objects["turtles"].get_active_turtle()
+ elif new_prim.wants_turtles():
+ first_arg = global_objects["turtles"]
+ elif new_prim.wants_canvas():
+ first_arg = global_objects["canvas"]
+ elif new_prim.wants_logocode():
+ first_arg = global_objects["logo"]
+ elif new_prim.wants_heap():
+ first_arg = global_objects["logo"].heap
+ elif new_prim.wants_tawindow():
+ first_arg = global_objects["window"]
# execute the actual function
- if first_arg is None or is_bound_instancemethod(self.func):
- return_value = self.func(*new_args, **new_kwargs)
+ if first_arg is None:
+ return_value = new_prim.func(*new_args, **new_kwargs)
else:
- return_value = self.func(first_arg, *new_args, **new_kwargs)
-
- if self.call_afterwards is not None:
- self.call_afterwards(*new_args, **new_kwargs)
-
+ return_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
+ """ Transform this object into a Python AST. When serialized and
executed, the AST will do exactly the same as calling this object. """
- # constant arguments
- (all_arg_asts, all_kwarg_asts) = self._add_constant_args(
- arg_asts, kwarg_asts, convert_to_ast=True)
-
- # slot wrappers
- (new_arg_asts, new_kwarg_asts) = self._apply_wrappers(
- all_arg_asts, all_kwarg_asts, convert_to_ast=True)
+ 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 #
@@ -347,11 +345,12 @@ class Primitive(object):
elif controller == Primitive.controller_while:
condition_ast = new_arg_asts[0].args[0]
elif controller == Primitive.controller_until:
- condition_ast = ast.UnaryOp(
- op=ast.Not, operand=new_arg_asts[0].args[0])
+ pos_cond_ast = new_arg_asts[0].args[0]
+ condition_ast = ast.UnaryOp(op=ast.Not,
+ operand=pos_cond_ast)
else:
- raise ValueError("unknown loop controller: " +
- repr(controller))
+ raise PyExportError("unknown loop controller: " +
+ repr(controller))
loop_ast = ast.While(test=condition_ast,
body=new_arg_asts[1],
orelse=[])
@@ -370,33 +369,41 @@ class Primitive(object):
# boxes
elif self == LogoCode.prim_set_box:
- id_str = 'BOX[%s]' % (repr(ast_to_value(new_arg_asts[0])))
- target_ast = ast.Name(id=id_str, ctx=ast.Store)
- value_ast = new_arg_asts[1]
- assign_ast = ast.Assign(targets=[target_ast], value=value_ast)
- return assign_ast
+ 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:
- id_str = 'BOX[%s]' % (repr(ast_to_value(new_arg_asts[0])))
- return ast.Name(id=id_str, ctx=ast.Load)
+ 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_name = ast_to_value(new_arg_asts[0])
- stack_func_name = 'ACTION[%s]' % (repr(stack_name))
- stack_func = ast.Name(id=stack_func_name, ctx=ast.Load)
- return get_call_ast('logo.icall', [stack_func])
+ 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__]
- # BEGIN hack for 'plus': unpack tuples
- if (self == Primitive.plus and len(new_arg_asts) == 1 and
- isinstance(new_arg_asts[0], (list, tuple)) and
- len(new_arg_asts[0]) == 2):
- new_arg_asts = new_arg_asts[0]
- # END hack for 'plus'
+ # '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]
@@ -412,56 +419,82 @@ class Primitive(object):
comparators=[right])
else:
return ast.BinOp(op=op, left=left, right=right)
- else:
- raise ValueError(("operator Primitive.%s got unexpected"
- " number of arguments (%d)")
- % (str(self.func.__func__.__name__),
- len(new_arg_asts)))
- # type conversion
- elif self in (Primitive.convert_for_cmp, Primitive.convert_to_number,
- Primitive.convert_for_plus):
- return self.func(*new_arg_asts, **new_kwarg_asts)
+ # 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:
- if len(new_arg_asts) == 1:
- return new_arg_asts[0]
+ return new_arg_asts[0]
+
+ # constant
+ elif self == CONSTANTS.get:
+ return TypedSubscript(value=ast.Name(id='CONSTANTS', ctx=ast.Load),
+ slice_=ast.Index(value=new_arg_asts[0]),
+ return_type=self.return_type)
+
+ # group of Primitives or sandwich-clamp block
+ elif self in (Primitive.group, LogoCode.prim_clamp):
+ ast_list = []
+ for prim in new_arg_asts[0]:
+ if export_me(prim):
+ new_ast = value_to_ast(prim)
+ if isinstance(new_ast, ast.AST):
+ ast_list.append(new_ast)
+ return ast_list
+
+ # comment
+ elif self == Primitive.comment:
+ if isinstance(new_arg_asts[0], ast.Str):
+ text = ' ' + str(new_arg_asts[0].s)
else:
- raise ValueError("Primitive.identity got unexpected number "
- "of arguments (%d)" % (len(new_arg_asts)))
-
- # tuples
- elif self == Primitive.make_tuple:
- if not new_kwarg_asts:
- return ast.Tuple(elts=new_arg_asts, ctx=ast.Load)
- else:
- raise ValueError("tuple constructor (Primitive.make_tuple) "
- "got unexpected arguments: " +
- repr(new_kwarg_asts))
-
- # group of Primitives
- elif self == Primitive.group:
- return new_arg_asts[0].elts
+ 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 = ""
- if self.wants_turtle():
- func_name = "turtle."
- elif self.wants_turtles():
- func_name = "turtles."
- elif self.wants_canvas():
- func_name = "canvas."
- elif self.wants_logocode():
- func_name = "logo."
- elif self.wants_tawindow():
- func_name = "tw."
- # get the name of the function directly from the function itself
- func_name += self.func.__name__
-
- return get_call_ast(func_name, new_arg_asts, new_kwarg_asts)
+ 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.
@@ -469,8 +502,9 @@ class Primitive(object):
# other is a Primitive
if isinstance(other, Primitive):
return (self == other.func and
- self.constant_args == other.constant_args and
- self.slot_wrappers == other.slot_wrappers and
+ self.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)
@@ -478,7 +512,7 @@ class Primitive(object):
elif callable(other):
if is_instancemethod(self.func) != is_instancemethod(other):
return False
- elif is_instancemethod(self.func): # and is_instancemethod(other)
+ 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:
@@ -492,7 +526,7 @@ class Primitive(object):
return False
def wants_turtle(self):
- """ Does this Primitive want to get the active turtle as its first
+ """ Does this Primitive want to get the active turtle as its first
argument? """
return self._wants(Turtle)
@@ -509,7 +543,13 @@ class Primitive(object):
def wants_logocode(self):
""" Does this Primitive want to get the LogoCode instance as its
first argument? """
- return self._wants(LogoCode)
+ 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
@@ -517,27 +557,18 @@ class Primitive(object):
return self._wants(TurtleArtWindow)
def wants_nothing(self):
- """ Does this Primitive want nothing as its first argument? I.e. does
- it want to be passed all the arguments of the block and nothing
+ """ Does this Primitive want nothing as its first argument? I.e. does
+ it want to be passed all the arguments of the block and nothing
else? """
return not is_instancemethod(self.func)
def _wants(self, theClass):
- if is_instancemethod(self.func):
- return self.func.im_class == theClass
- else:
- return False
+ 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 make_tuple(*values):
- """ This method corresponds to a Python tuple consisting of the given
- values. """
- return tuple(values)
-
- @staticmethod
def controller_repeat(num):
""" Loop controller for the 'repeat' block """
for i in range(num):
@@ -551,16 +582,22 @@ class Primitive(object):
yield True
@staticmethod
- def controller_while(boolean):
- """ Loop controller for the 'while' block """
- while boolean:
+ 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(boolean):
- """ Loop controller for the 'until' block """
- while not boolean:
+ 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
@@ -574,18 +611,18 @@ class Primitive(object):
return (callable(candidate)
and candidate in Primitive.LOOP_CONTROLLERS)
- # look at the first constant argument
- first_const = self.constant_args.get(0, None)
- if _is_loop_controller(first_const):
- return first_const
-
- # look at the first slot wrapper
- first_wrapper = self.slot_wrappers.get(0, None)
- if _is_loop_controller(first_wrapper):
- return first_wrapper
+ 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 ValueError("found no loop controller for " + repr(self))
+ raise PyExportError("found no loop controller for " + repr(self))
@staticmethod
def do_nothing():
@@ -606,67 +643,6 @@ class Primitive(object):
return return_val
@staticmethod
- def convert_for_plus(value1, value2):
- """ If at least one value is a string, convert both to a string.
- Otherwise, convert both to a number. (Colors are converted to an
- integer before they are converted to a string.) """
- convert_to_ast = False
- (value1_ast, value2_ast) = (None, None)
-
- if isinstance(value1, ast.AST):
- convert_to_ast = True
- value1_ast = value1
- value1 = ast_to_value(value1_ast)
- if isinstance(value2, ast.AST):
- value2_ast = value2
- value2 = ast_to_value(value2_ast)
-
- def _to_string(val, val_ast):
- """ Return strings as they are, convert Colors to an integer and
- then to a string, and convert everything else directly to a
- string. """
- val_conv = val
- val_conv_ast = val_ast
- if not isinstance(val, basestring):
- if isinstance(val, Color):
- conv_prim = Primitive(str, slot_wrappers={
- 0: Primitive(int)})
- else:
- conv_prim = Primitive(str)
- if not convert_to_ast:
- val_conv = conv_prim(val)
- else:
- val_conv_ast = conv_prim.get_ast(val_ast)
- return (val_conv, val_conv_ast)
-
- def _to_number(val, val_ast):
- """ Return numbers as they are, and convert everything else to an
- integer. """
- val_conv = val
- val_conv_ast = val_ast
- if not isinstance(val, (float, int, long)):
- conv_prim = Primitive(int)
- if not convert_to_ast:
- val_conv = conv_prim(val)
- else:
- val_conv_ast = conv_prim.get_ast(val_ast)
- return (val_conv, val_conv_ast)
-
- if isinstance(value1, basestring) or isinstance(value2, basestring):
- # convert both to strings
- (value1_conv, value1_conv_ast) = _to_string(value1, value1_ast)
- (value2_conv, value2_conv_ast) = _to_string(value2, value2_ast)
- else:
- # convert both to numbers
- (value1_conv, value1_conv_ast) = _to_number(value1, value1_ast)
- (value2_conv, value2_conv_ast) = _to_number(value2, value2_ast)
-
- if convert_to_ast:
- return (value1_conv_ast, value2_conv_ast)
- else:
- return (value1_conv, value2_conv)
-
- @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
@@ -680,77 +656,6 @@ class Primitive(object):
return arg1 + arg2
@staticmethod
- def convert_to_number(value, decimal_point='.'):
- """ Convert value to a number. If value is an AST, another AST is
- wrapped around it to represent the conversion, e.g.,
- Str(s='1.2') -> Call(func=Name('float'), args=[Str(s='1.2')])
- 1. Return all numbers (float, int, long) unchanged.
- 2. Convert a string containing a number into a float.
- 3. Convert a single character to its ASCII integer value.
- 4. Extract the first element of a list and convert it to a number.
- 5. Convert a Color to a float.
- If the value cannot be converted to a number and the value is not
- an AST, return None. If it is an AST, return an AST representing
- `float(value)'. """ # TODO find a better solution
- # 1. number
- if isinstance(value, (float, int, long, ast.Num)):
- return value
-
- converted = None
- conversion_ast = None
- convert_to_ast = False
- if isinstance(value, ast.AST):
- convert_to_ast = True
- value_ast = value
- value = ast_to_value(value_ast)
- if isinstance(decimal_point, ast.AST):
- decimal_point = ast_to_value(decimal_point)
-
- # 2./3. string
- if isinstance(value, basestring):
- if convert_to_ast:
- conversion_ast = Primitive.convert_for_cmp(value_ast,
- decimal_point)
- if not isinstance(conversion_ast, ast.Num):
- converted = None
- else:
- converted = Primitive.convert_for_cmp(value, decimal_point)
- if not isinstance(converted, (float, int, long)):
- converted = None
- # 4. list
- elif isinstance(value, list):
- if value:
- number = Primitive.convert_to_number(value[0])
- if convert_to_ast:
- conversion_ast = number
- else:
- converted = number
- else:
- converted = None
- if convert_to_ast:
- conversion_ast = get_call_ast('float', [value_ast])
- # 5. Color
- elif isinstance(value, Color):
- converted = float(value)
- if convert_to_ast:
- conversion_ast = get_call_ast('float', [value_ast])
- else:
- converted = None
- if convert_to_ast:
- conversion_ast = get_call_ast('float', [value_ast])
-
- if convert_to_ast:
- if conversion_ast is None:
- return value_ast
- else:
- return conversion_ast
- else:
- if converted is None:
- return value
- else:
- return converted
-
- @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. """
@@ -767,7 +672,7 @@ class Primitive(object):
@staticmethod
def divide(arg1, arg2):
""" Divide the first argument by the second """
- return arg1 / arg2
+ return float(arg1) / arg2
@staticmethod
def modulo(arg1, arg2):
@@ -782,20 +687,12 @@ class Primitive(object):
return arg1 ** arg2
@staticmethod
- def integer_division(arg1, arg2):
- """ Divide the first argument by the second and return the integer
- that is smaller than or equal to the result """
- return arg1 // arg2
-
- @staticmethod
- def bitwise_and(arg1, arg2):
- """ Return the bitwise AND of the two arguments """
- return arg1 & arg2
-
- @staticmethod
- def bitwise_or(arg1, arg2):
- """ Return the bitwise OR of the two arguments """
- return arg1 | arg2
+ 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):
@@ -814,59 +711,6 @@ class Primitive(object):
return not arg
@staticmethod
- def convert_for_cmp(value, decimal_point='.'):
- """ Convert value such that it can be compared to something else. If
- value is an AST, another AST is wrapped around it to represent the
- conversion, e.g.,
- Str(s='a') -> Call(func=Name('ord'), args=[Str(s='a')])
- 1. Convert a string containing a number into a float.
- 2. Convert a single character to its ASCII integer value.
- 3. Return all other values unchanged. """
- converted = None
- conversion_ast = None
- convert_to_ast = False
- if isinstance(value, ast.AST):
- convert_to_ast = True
- value_ast = value
- value = ast_to_value(value_ast)
- if isinstance(decimal_point, ast.AST):
- decimal_point = ast_to_value(decimal_point)
-
- if isinstance(value, basestring):
- # 1. string containing a number
- replaced = value.replace(decimal_point, '.')
- try:
- converted = float(replaced)
- except ValueError:
- pass
- else:
- if convert_to_ast:
- conversion_ast = get_call_ast('float', [value_ast])
-
- # 2. single character
- if converted is None:
- try:
- converted = ord(value)
- except TypeError:
- pass
- else:
- if convert_to_ast:
- conversion_ast = get_call_ast('ord', [value_ast])
-
- # 3. normal string or other type of value (nothing to do)
-
- if convert_to_ast:
- if conversion_ast is None:
- return value_ast
- else:
- return conversion_ast
- else:
- if converted is None:
- return value
- else:
- return converted
-
- @staticmethod
def equals(arg1, arg2):
""" Return arg1 == arg2 """
return arg1 == arg2
@@ -881,23 +725,339 @@ class Primitive(object):
""" 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 is_instancemethod(method):
- # TODO how to access the type `instancemethod` directly?
- return type(method).__name__ == "instancemethod"
+ 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)
-def is_bound_instancemethod(method):
- return is_instancemethod(method) and method.im_self is not None
+class ArgSlotDisjunction(Disjunction,ArgSlot):
+ """ Disjunction of two or more argument slots """
+ pass
-def is_unbound_instancemethod(method):
- return is_instancemethod(method) and method.im_self is None
+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 is_staticmethod(method):
- # TODO how to access the type `staticmethod` directly?
- return type(method).__name__ == "staticmethod"
+ 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):
@@ -905,20 +1065,25 @@ def value_to_ast(value, *args_for_prim, **kwargs_for_prim):
bool, basestring, list
If the value is already an AST, return it unchanged.
If the value is a non-exportable Primitive, return None. """
- # TODO media
+ # 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:
@@ -926,54 +1091,24 @@ def value_to_ast(value, *args_for_prim, **kwargs_for_prim):
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):
- if str(value) in CONSTANTS:
- # repr(str(value)) is necessary; it first converts the Color to a
- # string and then adds appropriate quotes around that string
- return ast.Name(id='CONSTANTS[%s]' % repr(str(value)),
- ctx=ast.Load)
- else:
- # 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])
- else:
- raise ValueError("unknown type of raw value: " + repr(type(value)))
-
-
-def ast_to_value(ast_object):
- """ Retrieve the value out of a value AST. Supported AST types:
- Num, Str, Name, List, Tuple, Set
- If no value can be extracted, return None. """
- if isinstance(ast_object, ast.Num):
- return ast_object.n
- elif isinstance(ast_object, ast.Str):
- return ast_object.s
- elif isinstance(ast_object, (ast.List, ast.Tuple, ast.Set)):
- return ast_object.elts
- elif (isinstance(ast_object, ast.Name)):
- try:
- return eval(ast_object.id)
- except NameError:
- return None
+ # 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:
- return None
+ raise PyExportError("unknown type of raw value: " + repr(type(value)))
-def get_call_ast(func_name, args=[], keywords={}):
- return ast.Call(func=ast.Name(id=func_name,
- ctx=ast.Load),
- args=args,
- keywords=keywords,
- starargs=None,
- kwargs=None)
-
-
-def call_me(something):
- """ Return True iff this is a Primitive and its call_me attribute is
- True, i.e. nothing is callable except for Primitives with
- call_me == True """
- return isinstance(something, Primitive) and something.call_me
+def ast_yield_true():
+ return ast.Yield(value=ast.Name(id='True', ctx=ast.Load))
def export_me(something):
@@ -981,3 +1116,5 @@ def export_me(something):
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/tatype.py b/TurtleArt/tatype.py
new file mode 100644
index 0000000..359b5fc
--- /dev/null
+++ b/TurtleArt/tatype.py
@@ -0,0 +1,444 @@
+#Copyright (c) 2013 Marion Zepf
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+""" type system for Primitives and their arguments """
+
+import ast
+
+from tablock import Media
+from taconstants import (Color, CONSTANTS)
+from tautils import debug_output
+
+
+class Type(object):
+ """ A type in the type hierarchy. """
+
+ def __init__(self, constant_name, value):
+ """ constant_name -- the name of the constant that points to this Type
+ object
+ value -- an arbitrary integer that is different from the values of
+ all other Types. The order of the integers doesn't matter. """
+ self.constant_name = constant_name
+ self.value = value
+
+ def __eq__(self, other):
+ if other is None:
+ return False
+ if not isinstance(other, Type):
+ return False
+ return self.value == other.value
+
+ def __str__(self):
+ return str(self.constant_name)
+ __repr__ = __str__
+
+
+class TypeDisjunction(tuple,Type):
+ """ Disjunction of two or more Types (from the type hierarchy) """
+
+ def __init__(self, iterable):
+ self = tuple(iterable)
+
+ def __str__(self):
+ s = ["("]
+ for disj in self:
+ s.append(str(disj))
+ s.append(" or ")
+ s.pop()
+ s.append(")")
+ return "".join(s)
+
+
+# individual types
+TYPE_OBJECT = Type('TYPE_OBJECT', 0)
+TYPE_BOOL = Type('TYPE_BOOL', 5)
+TYPE_BOX = Type('TYPE_BOX', 8) # special type for the unknown content of a box
+TYPE_CHAR = Type('TYPE_CHAR', 1)
+TYPE_COLOR = Type('TYPE_COLOR', 2)
+TYPE_FLOAT = Type('TYPE_FLOAT', 3)
+TYPE_INT = Type('TYPE_INT', 4)
+TYPE_MEDIA = Type('TYPE_MEDIA', 10)
+TYPE_NUMBER = Type('TYPE_NUMBER', 6) # shortcut to avoid a TypeDisjunction
+ # between TYPE_FLOAT and TYPE_INT
+TYPE_NUMERIC_STRING = Type('TYPE_NUMERIC_STRING', 7)
+TYPE_STRING = Type('TYPE_STRING', 9)
+
+# groups/ classes of types
+TYPES_NUMERIC = (TYPE_FLOAT, TYPE_INT, TYPE_NUMBER)
+
+
+BOX_AST = ast.Name(id='BOX', ctx=ast.Load)
+ACTION_AST = ast.Name(id='ACTION', ctx=ast.Load)
+
+
+def get_type(x):
+ """ Return the most specific type in the type hierarchy that applies to x
+ and a boolean indicating whether x is an AST. If the type cannot be
+ determined, return TYPE_OBJECT as the type. """
+ # non-AST types
+ if isinstance(x, (int, long)):
+ return (TYPE_INT, False)
+ elif isinstance(x, float):
+ return (TYPE_FLOAT, False)
+ elif isinstance(x, basestring):
+ if len(x) == 1:
+ return (TYPE_CHAR, False)
+ try:
+ float(x)
+ except ValueError:
+ return (TYPE_STRING, False)
+ else:
+ return (TYPE_NUMERIC_STRING, False)
+ elif isinstance(x, Color):
+ return (TYPE_COLOR, False)
+ elif isinstance(x, Media):
+ return (TYPE_MEDIA, False)
+ elif hasattr(x, "return_type"):
+ return (x.return_type, False)
+
+ # AST types
+ elif isinstance(x, ast.Num):
+ return (get_type(x.n)[0], True)
+ elif isinstance(x, ast.Str):
+ return (get_type(x.s)[0], True)
+ elif isinstance(x, ast.Name):
+ try:
+ # we need to have imported CONSTANTS for this to work
+ value = eval(x.id)
+ except NameError:
+ return (TYPE_OBJECT, True)
+ else:
+ return (get_type(value)[0], True)
+ elif isinstance(x, ast.Subscript):
+ if x.value == BOX_AST:
+ return (TYPE_BOX, True)
+ elif isinstance(x, ast.Call):
+ if isinstance(x.func, ast.Name):
+ if x.func.id == 'float':
+ return (TYPE_FLOAT, True)
+ elif x.func.id in ('int', 'ord'):
+ return (TYPE_INT, True)
+ elif x.func.id == 'chr':
+ return (TYPE_CHAR, True)
+ elif x.func.id in ('repr', 'str', 'unicode'):
+ return (TYPE_STRING, True)
+ elif x.func.id == 'Color':
+ return (TYPE_COLOR, True)
+ elif x.func.id == 'Media':
+ return (TYPE_MEDIA, True)
+ # unary operands never change the type of their argument
+ elif isinstance(x, ast.UnaryOp):
+ if issubclass(x.op, ast.Not):
+ # 'not' always returns a boolean
+ return (TYPE_BOOL, True)
+ else:
+ return get_type(x.operand)
+ # boolean and comparison operators always return a boolean
+ if isinstance(x, (ast.BoolOp, ast.Compare)):
+ return (TYPE_BOOL, True)
+ # other binary operators
+ elif isinstance(x, ast.BinOp):
+ type_left = get_type(x.left)[0]
+ type_right = get_type(x.right)[0]
+ if type_left == TYPE_STRING or type_right == TYPE_STRING:
+ return (TYPE_STRING, True)
+ if type_left == type_right == TYPE_INT:
+ return (TYPE_INT, True)
+ else:
+ return (TYPE_FLOAT, True)
+
+ return (TYPE_OBJECT, isinstance(x, ast.AST))
+
+
+def is_instancemethod(method):
+ # TODO how to access the type `instancemethod` directly?
+ return type(method).__name__ == "instancemethod"
+
+def is_bound_method(method):
+ return ((is_instancemethod(method) and method.im_self is not None) or
+ (hasattr(method, '__self__') and method.__self__ is not None))
+
+def is_staticmethod(method):
+ # TODO how to access the type `staticmethod` directly?
+ return type(method).__name__ == "staticmethod"
+
+
+def identity(x):
+ return x
+
+TYPE_CONVERTERS = {
+ # Type hierarchy: If there is a converter A -> B, then A is a subtype of B.
+ # The converter from A to B is stored under TYPE_CONVERTERS[A][B].
+ # The relation describing the type hierarchy must be transitive, i.e.
+ # converting A -> C must yield the same result as converting A -> B -> C.
+ # TYPE_OBJECT is the supertype of everything.
+ TYPE_BOX: {
+ TYPE_FLOAT: float,
+ TYPE_INT: int,
+ TYPE_NUMBER: float,
+ TYPE_STRING: str},
+ TYPE_CHAR: {
+ TYPE_INT: ord,
+ TYPE_STRING: identity},
+ TYPE_COLOR: {
+ TYPE_FLOAT: float,
+ TYPE_INT: int,
+ TYPE_NUMBER: int,
+ TYPE_STRING: Color.get_number_string},
+ TYPE_FLOAT: {
+ TYPE_INT: int,
+ TYPE_NUMBER: identity,
+ TYPE_STRING: str},
+ TYPE_INT: {
+ TYPE_FLOAT: float,
+ TYPE_NUMBER: identity,
+ TYPE_STRING: str},
+ TYPE_NUMBER: {
+ TYPE_FLOAT: float,
+ TYPE_INT: int,
+ TYPE_STRING: str},
+ TYPE_NUMERIC_STRING: {
+ TYPE_FLOAT: float,
+ TYPE_STRING: identity}
+}
+
+
+class TATypeError(BaseException):
+ """ TypeError with the types from the hierarchy, not with Python types """
+
+ def __init__(self, bad_value, bad_type=None, req_type=None, message=''):
+ """ bad_value -- the mis-typed value that caused the error
+ bad_type -- the type of the bad_value
+ req_type -- the type that the value was expected to have
+ message -- short statement about the cause of the error. It is
+ not shown to the user, but may appear in debugging output. """
+ self.bad_value = bad_value
+ self.bad_type = bad_type
+ self.req_type = req_type
+ self.message = message
+
+ def __str__(self):
+ msg = []
+ if self.message:
+ msg.append(self.message)
+ msg.append(" (")
+ msg.append("bad value: ")
+ msg.append(repr(self.bad_value))
+ if self.bad_type is not None:
+ msg.append(", bad type: ")
+ msg.append(repr(self.bad_type))
+ if self.req_type is not None:
+ msg.append(", req type: ")
+ msg.append(repr(self.req_type))
+ if self.message:
+ msg.append(")")
+ return "".join(msg)
+ __repr__ = __str__
+
+
+def get_converter(old_type, new_type):
+ """ If there is a converter old_type -> new_type, return it. Else return
+ None. If a chain of converters is necessary, return it as a tuple or
+ list (starting with the innermost, first-to-apply converter). """
+ # every type can be converted to TYPE_OBJECT
+ if new_type == TYPE_OBJECT:
+ return identity
+ # every type can be converted to itself
+ if old_type == new_type:
+ return identity
+
+ # is there a converter for this pair of types?
+ converters_from_old = TYPE_CONVERTERS.get(old_type)
+ if converters_from_old is None:
+ return None
+ converter = converters_from_old.get(new_type)
+ if converter is not None:
+ return converter
+ else:
+ # form the transitive closure of all types that old_type can be
+ # converted to, and look for new_type there
+ backtrace = converters_from_old.copy()
+ new_backtrace = backtrace.copy()
+ break_all = False
+ while True:
+ newest_backtrace = {}
+ for t in new_backtrace:
+ for new_t in TYPE_CONVERTERS.get(t, {}):
+ if new_t not in backtrace:
+ newest_backtrace[new_t] = t
+ backtrace[new_t] = t
+ if new_t == new_type:
+ break_all = True
+ break
+ if break_all:
+ break
+ if break_all or not newest_backtrace:
+ break
+ new_backtrace = newest_backtrace
+ # use the backtrace to find the path from old_type to new_type
+ if new_type in backtrace:
+ converter_chain = []
+ t = new_type
+ while t in backtrace and isinstance(backtrace[t], Type):
+ converter_chain.insert(0, TYPE_CONVERTERS[backtrace[t]][t])
+ t = backtrace[t]
+ converter_chain.insert(0, TYPE_CONVERTERS[old_type][t])
+ return converter_chain
+ return None
+
+
+def convert(x, new_type, old_type=None, converter=None):
+ """ Convert x to the new type if possible.
+ old_type -- the type of x. If not given, it is computed. """
+ if not isinstance(new_type, Type):
+ raise ValueError('%s is not a type in the type hierarchy'
+ % (repr(new_type)))
+ # every type can be converted to TYPE_OBJECT
+ if new_type == TYPE_OBJECT:
+ return x
+ if not isinstance(old_type, Type):
+ (old_type, is_an_ast) = get_type(x)
+ else:
+ is_an_ast = isinstance(x, ast.AST)
+ # every type can be converted to itself
+ if old_type == new_type:
+ return x
+
+ # special case: 'box' block (or 'pop' block) as an AST
+ if is_an_ast and old_type == TYPE_BOX:
+ new_type_ast = ast.Name(id=new_type.constant_name)
+ return get_call_ast('convert', [x, new_type_ast], return_type=new_type)
+
+ # if the converter is not given, try to find one
+ if converter is None:
+ converter = get_converter(old_type, new_type)
+ if converter is None:
+ # no converter available
+ raise TATypeError(bad_value=x, bad_type=old_type,
+ req_type=new_type, message=("found no converter"
+ " for this type combination"))
+
+ def _apply_converter(converter, y):
+ 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/tawindow.py b/TurtleArt/tawindow.py
index 17ca81d..948f1f4 100644
--- a/TurtleArt/tawindow.py
+++ b/TurtleArt/tawindow.py
@@ -58,7 +58,7 @@ from taconstants import (HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE,
PYTHON_SKIN, PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK,
EXPANDABLE_ARGS, XO1, XO15, XO175, XO30, XO4, TITLEXY,
CONTENT_ARGS, CONSTANTS, EXPAND_SKIN, PROTO_LAYER,
- EXPANDABLE_FLOW, SUFFIX, TMP_SVG_PATH)
+ EXPANDABLE_FLOW, SUFFIX, TMP_SVG_PATH, Color)
from tapalette import (palette_names, palette_blocks, expandable_blocks,
block_names, content_blocks, default_values,
special_names, block_styles, help_strings,
@@ -67,7 +67,7 @@ from tapalette import (palette_names, palette_blocks, expandable_blocks,
palette_init_on_start)
from talogo import (LogoCode, primitive_dictionary, logoerror)
from tacanvas import TurtleGraphics
-from tablock import (Blocks, Block)
+from tablock import (Blocks, Block, Media, media_blocks_dictionary)
from taturtle import (Turtles, Turtle)
from tautils import (magnitude, get_load_name, get_save_name, data_from_file,
data_to_file, round_int, get_id, get_pixbuf_from_journal,
@@ -77,7 +77,7 @@ from tautils import (magnitude, get_load_name, get_save_name, data_from_file,
find_block_to_run, find_top_block, journal_check,
find_group, find_blk_below, data_to_string,
find_start_stack, get_hardware, debug_output,
- error_output, convert, find_hat, find_bot_block,
+ error_output, find_hat, find_bot_block,
restore_clamp, collapse_clamp, data_from_string,
increment_name, get_screen_dpi)
from tasprite_factory import (SVG, svg_str_to_pixbuf, svg_from_file)
@@ -4359,6 +4359,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 '''
@@ -4630,7 +4684,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,
@@ -4639,7 +4692,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'),
@@ -4659,7 +4711,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,
@@ -4668,8 +4719,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'),
@@ -4689,7 +4738,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')],
@@ -4699,52 +4747,11 @@ before making changes to your program'))
default=[name, 100],
help_string=_('stores numeric value in named \
variable'))
- self.lc.def_prim(
- 'storeinbox',
- 2,
- lambda self, x, y: primitive_dictionary['setbox']('box3', x, y))
# Regenerate the palette, which will now include the new block.
self.show_toolbar_palette(palette_name_to_index('blocks'),
regenerate=True)
- def _prim_stack(self, x):
- ''' Process a named stack '''
- if isinstance(convert(x, float, False), float):
- if int(float(x)) == x:
- x = int(x)
- if 'stack3' + str(x) not in self.lc.stacks or \
- self.lc.stacks['stack3' + str(x)] is None:
- raise logoerror('#nostack')
- self.lc.icall(self.lc.evline,
- self.lc.stacks['stack3' + str(x)][:])
- yield True
- self.lc.procstop = False
- self.lc.ireturn()
- yield True
-
- def _prim_box(self, x):
- ''' Retrieve value from named box '''
- if isinstance(convert(x, float, False), float):
- if int(float(x)) == x:
- x = int(x)
- try:
- return self.lc.boxes['box3' + str(x)]
- except KeyError:
- raise logoerror('#emptybox')
-
- def _prim_setbox(self, name, x, val):
- ''' Define value of named box '''
- if x is not None:
- if isinstance(convert(x, float, False), float):
- if int(float(x)) == x:
- x = int(x)
- self.lc.boxes[name + str(x)] = val
- self.lc.update_label_value('box', val, label=x)
- else:
- self.lc.boxes[name] = val
- self.lc.update_label_value(name, val)
-
def dock_dx_dy(self, block1, dock1n, block2, dock2n):
''' Find the distance between the dock points of two blocks. '''
# Cannot dock a block to itself
diff --git a/doc/primitives-with-arguments.md b/doc/primitives-with-arguments.md
new file mode 100644
index 0000000..2237898
--- /dev/null
+++ b/doc/primitives-with-arguments.md
@@ -0,0 +1,125 @@
+How to define Primitive objects for blocks with arguments
+=========================================================
+
+The tutorials in this document assume that the reader is able to
+add simple blocks without arguments to Turtle Art. Please refer
+to the module documentation of ../TurtleArt/tabasics.py for a
+tutorial on that.
+
+Example 1: Block with one Argument
+----------------------------------
+
+In this example, we define the `Primitive` object for a block
+that increases the pen color by a numeric argument that comes
+from another block. In Turtle Art, the block looks like this:
+
+ ,---.___,---------.
+ / |
+ | increment color |=
+ \ |
+ `---.___,---------ยด
+
+When the block is executed, we want it to do the same as the
+following statement:
+
+ Turtle.set_pen_color(plus(Turtle.get_pen_color(), ...))
+
+where `...` stands for the output of the block connected to the
+right hand dock of our block. For arguments not known in
+advance, we need to insert a placeholder in the form of an
+`ArgSlot` object. An `ArgSlot` object describes some properties
+of the argument it receives. It defines the type of the
+argument, it knows whether the argument needs to be called (if
+it is callable), and it knows which callable (if any) it must
+wrap around the argument before consuming it. (For more on slot
+wrappers, please refer to the other examples below.) For this
+example, we can use the default values for the second and third
+property (`True` and `None`, respectively). We only need to
+state the first one, the argument type, explicitly:
+
+ prim_inc_color = Primitive(Turtle.set_pen_color,
+ arg_descs=[ConstantArg(Primitive(
+ Primitive.plus, return_type=TYPE_NUMBER,
+ arg_descs=[ConstantArg(Primitive(
+ Turtle.get_pen_color, return_type=TYPE_NUMBER)),
+ ArgSlot(TYPE_NUMBER)]))])
+
+ self.tw.lc.def_prim('inc_color', 0, prim_inc_color)
+
+Turtle Art uses the same type system for argument types as for
+the return types of Primitive objects. If a value block (such as
+the number block) is attached to the right hand dock of the
+'increment color' block, then Turtle Art matches the value of
+that block against the type requirement of the argument slot. If
+the attached block has a Primitive object (such as the 'plus'
+block), then that Primitive's return value is matched against
+the required type. If Turtle Art doesn't know how to convert the
+attached value to the required type, it shows the user an error
+message during execution.
+
+
+Example 2: Block with a Slot Wrapper
+------------------------------------
+
+In Turtle Art, moving the turtle backward by x is the same as
+moving it forward by negative x (or -x). In fact, the 'back'
+block uses the same method (`Turtle.forward`) as the 'forward'
+block. But the 'back' block needs to switch the sign of its
+argument before passing it to `Turtle.forward`. I.e. it needs to
+execute the following statement:
+
+ Turtle.forward(minus(...))
+
+where `...` again stands for the output of the block connected
+to the right hand dock of the 'back' block. This is where slot
+wrappers come in helpful. A slot wrapper is a Primitive that is
+'wrapped around' an argument of its 'parent' Primitive. Slot
+wrappers can only be attached to `ArgSlot` objects, that is, to
+arguments that come from other blocks. In the case of the 'back'
+block, this looks as follows:
+
+ Primitive(Turtle.forward,
+ arg_descs=[ArgSlot(TYPE_NUMBER,
+ wrapper=Primitive(
+ Primitive.minus, return_type=TYPE_NUMBER,
+ arg_descs=[ArgSlot(TYPE_NUMBER)]))],
+ call_afterwards=self.after_move))
+
+When the 'back' block is called, it passes the argument that it
+gets from its right hand dock to the `ArgSlot` object. That, in
+turn, passes it to its wrapper, and then matches the type of the
+return value of the wrapper against its type requirement. If the
+types match, the wrapper's return value is passed back to the
+function of the main Primitive, `Turtle.forward`.
+
+Note that slot wrappers and Primitive objects can be nested
+inside each other infinitely deeply.
+
+
+Example 3: Block with a Group of Primitives
+-------------------------------------------
+
+Blocks like the 'clean' block need to do several things in a
+row. E.g., the 'clean' block needs to tell the plugins that the
+screen is being cleared, it needs to stop media execution, clear
+the screen, and reset all turtles. It takes no block arguments,
+so it looks like this in Turtle Art:
+
+ ,---.___,---.
+ / \
+ | clean |
+ \ /
+ `---.___,---ยด
+
+To execute a series of several Primitives, we need to define a
+'group' of Primitives. This 'group' is itself a Primitive, using
+the special function `Primitive.group`. When called, it loops
+over its arguments and calls them successively. The Primitive
+object for the 'clean' block looks like this:
+
+ Primitive(Primitive.group, arg_descs=[ConstantArg([
+ Primitive(self.tw.clear_plugins),
+ Primitive(self.tw.lc.prim_clear_helper,
+ export_me=False),
+ Primitive(self.tw.canvas.clearscreen),
+ Primitive(self.tw.turtles.reset_turtles)])])
diff --git a/doc/type-system.md b/doc/type-system.md
new file mode 100644
index 0000000..4615dd1
--- /dev/null
+++ b/doc/type-system.md
@@ -0,0 +1,116 @@
+The TA Type System
+==================
+
+Why do we Need a Type System?
+-----------------------------
+
+The purpose of the type system is to have a consistent and
+standardized way of type-checking and type-converting the
+arguments of blocks. For example, the 'minus' block takes two
+arguments of type TYPE_NUMBER. But that doesn't mean that only
+number blocks can be attached to its argument docks. In fact,
+colors, single characters, and numeric strings (like `"-5.2"`)
+can easily be converted to numbers. The type system takes care
+of this. When e.g., a color is attached to the argument dock of
+the 'minus' block, the type system tries to find a converter
+from the type TYPE_COLOR (the type of the color block) to the
+type TYPE_NUMBER. If it finds one (and in this case it does),
+then the converter is applied to the argument. A converter is
+simply a callable (usually a function) and applying it simply
+means calling it and passing it the value of the argument block
+as a parameter. The converter returns the number that cor-
+responds to the color, and the number is passed on to the
+'minus' block. This way, the 'minus' block does not have to know
+about colors, characters, or numeric strings. Likewise, the
+color block also does not have to care about how its value can
+be converted to a number.
+
+Why do some Blocks Need Return Types?
+-------------------------------------
+
+The argument to the 'minus' block (to continue our example) need
+not be a simple value block; it can also be the result of a
+complex mathematical operation, i.e. the return type of another
+block such as 'multiply'. The 'minus' block still demands a
+value of type TYPE_NUMBER, so the 'multiply' block must provide
+information about its return type. This is why blocks that can
+be used as arguments to other blocks must specify a return type.
+
+What if I want to Specify Two Types at the Same Time?
+-----------------------------------------------------
+
+You can use the function `or_` (defined in `taprimitive.py`) to
+create disjunctions of `Primitive`s, argument lists, `ArgSlot`s,
+or types. Simply pass the disjuncts to it as its arguments.
+E.g., to create a disjunction between the types TYPE_NUMBER and
+TYPE_STRING, call
+
+ or_(TYPE_NUMBER, TYPE_STRING)
+
+The return value of the `or_` function will in this case be a
+`TypeDisjunction` object that holds the two types. It means the
+same as 'TYPE_NUMBER or TYPE_STRING' in English. The `or_`
+function knows automatically from the type of its arguments
+which type of object it must return.
+
+What if it is Impossible to Predict the Return Type of a Block?
+---------------------------------------------------------------
+
+In the case of the 'box' block, for example, it is impossible to
+predict what type it will return at runtime because one cannot
+foresee what will be inside the box at runtime. (E.g., the box
+contents could depend on input from the keyboard or camera.)
+This is where the special type TYPE_BOX comes in handy. It
+allows you to postpone the search for a type converter until the
+type of the box contents is known. As soon as this is the case,
+the type system will automatically apply the appropriate type
+converter.
+
+How to Add a New Type
+---------------------
+
+To add a new type to the type system, you need to instantiate a
+new `Type` object and store it in a constant whose name starts
+with `TYPE_`. You would do this in `tatype.py`:
+
+ TYPE_MYTYPE = Type('TYPE_MYTYPE', 99)
+
+The number argument to the `Type` constructor can have an
+arbitrary value, as long as it is different from the value of
+every other `Type` object.
+
+You also need to tell the type system how to recognize runtime
+objects that belong to your type. Add one or several new `elif`
+clauses to the `get_type` function. E.g., if you are defining a
+new type for dictionaries, you would add the clauses
+
+ elif isinstance(x, dict):
+ return (TYPE_DICT, False)
+ elif isinstance(x, ast.Dict):
+ return (TYPE_DICT, True)
+
+The second item of the tuple that `get_type` returns indicates
+whether `x` is an AST (Abstract Syntax Tree) or not. Only
+instances of subclasses of `ast.AST` are ASTs.
+
+Optionally, you can add converters for the new type. You can do
+so by extending the dictionary `TYPE_CONVERTERS` in `tatype.py`.
+The format is quite simple: To add a converter from your type to
+e.g., TYPE_FLOAT, add the entry:
+
+ TYPE_CONVERTERS = {
+ # ...
+ TYPE_MYTYPE: {
+ # ...
+ TYPE_FLOAT: float
+ # ...
+ }
+ # ...
+ }
+
+Note that it is not obligatory to use the function `float` as
+the converter to the type TYPE_FLOAT. In fact, you can use any
+function or method. Please make sure that the converter accepts
+arguments of the source type (here, TYPE_MYTYPE) and returns a
+value of the target type (here, TYPE_FLOAT). The converter must
+not throw any errors.
diff --git a/plugins/turtle_blocks_extras/turtle_blocks_extras.py b/plugins/turtle_blocks_extras/turtle_blocks_extras.py
index ae257d6..4a6abb9 100644
--- a/plugins/turtle_blocks_extras/turtle_blocks_extras.py
+++ b/plugins/turtle_blocks_extras/turtle_blocks_extras.py
@@ -32,14 +32,15 @@ from TurtleArt.talogo import (primitive_dictionary, logoerror,
media_blocks_dictionary)
from TurtleArt.taconstants import (DEFAULT_SCALE, ICON_SIZE, CONSTANTS,
MEDIA_SHAPES, SKIN_PATHS, BLOCKS_WITH_SKIN,
- PYTHON_SKIN, PREFIX_DICTIONARY, VOICES,
+ PYTHON_SKIN, MEDIA_BLOCK2TYPE, VOICES,
MACROS, Color)
from TurtleArt.tautils import (round_int, debug_output, get_path,
data_to_string, find_group, image_to_base64,
- hat_on_top, listify, data_from_file,
- data_to_file, chooser_dialog, get_load_name)
-from TurtleArt.tajail import (myfunc, myfunc_import)
-from TurtleArt.taprimitive import Primitive
+ hat_on_top, listify, data_from_file)
+from TurtleArt.tajail import myfunc_import
+from TurtleArt.taprimitive import (ArgSlot, ConstantArg, Primitive)
+from TurtleArt.tatype import (TYPE_BOOL, TYPE_BOX, TYPE_CHAR, TYPE_INT,
+ TYPE_FLOAT, TYPE_OBJECT, TYPE_STRING)
def _num_type(x):
@@ -92,7 +93,6 @@ class Turtle_blocks_extras(Plugin):
colors=["#FFC000", "#A08000"],
help_string=_('Palette of flow operators'))
- # internally expanded macro
palette.add_block('while',
style='clamp-style-boolean',
label=_('while'),
@@ -101,13 +101,16 @@ class Turtle_blocks_extras(Plugin):
special_name=_('while'),
help_string=_('do-while-True operator that uses \
boolean operators from Numbers palette'))
- # Primitive is only used for exporting this block, not for running it
self.tw.lc.def_prim('while', 2,
Primitive(self.tw.lc.prim_loop,
- slot_wrappers={0: Primitive(Primitive.controller_while)}),
+ arg_descs=[
+ ArgSlot(TYPE_OBJECT,
+ call_arg=False,
+ wrapper=Primitive(Primitive.controller_while,
+ arg_descs=[ArgSlot(TYPE_BOOL, call_arg=False)])),
+ ArgSlot(TYPE_OBJECT)]),
True)
- # internally expanded macro
palette.add_block('until',
style='clamp-style-boolean',
label=_('until'),
@@ -116,20 +119,26 @@ boolean operators from Numbers palette'))
special_name=_('until'),
help_string=_('do-until-True operator that uses \
boolean operators from Numbers palette'))
- # Primitive is only used for exporting this block, not for running it
self.tw.lc.def_prim('until', 2,
Primitive(self.tw.lc.prim_loop,
- slot_wrappers={0: Primitive(Primitive.controller_until)}),
+ arg_descs=[
+ ArgSlot(TYPE_OBJECT,
+ call_arg=False,
+ # TODO can we use controller_while in combination with not_?
+ wrapper=Primitive(Primitive.controller_until,
+ arg_descs=[ArgSlot(TYPE_BOOL, call_arg=False)])),
+ ArgSlot(TYPE_OBJECT)]),
True)
- primitive_dictionary['clamp'] = self._prim_clamp
palette.add_block('sandwichclamp',
style='clamp-style-collapsible',
label=' ',
special_name=_('top'),
prim_name='clamp',
help_string=_('top of a collapsible stack'))
- self.tw.lc.def_prim('clamp', 1, primitive_dictionary['clamp'], True)
+ self.tw.lc.def_prim('clamp', 1,
+ Primitive(self.tw.lc.prim_clamp, arg_descs=[ArgSlot(TYPE_OBJECT)]),
+ True)
def _media_palette(self):
debug_output('creating %s palette' % _('media'),
@@ -145,7 +154,7 @@ boolean operators from Numbers palette'))
default='None',
special_name=_('journal'),
help_string=_('Sugar Journal media object'))
- PREFIX_DICTIONARY['journal'] = '#smedia_'
+ MEDIA_BLOCK2TYPE['journal'] = 'media'
BLOCKS_WITH_SKIN.append('journal')
MEDIA_SHAPES.append('journalsmall')
MEDIA_SHAPES.append('journaloff')
@@ -158,7 +167,7 @@ boolean operators from Numbers palette'))
default='None',
help_string=_('Sugar Journal audio object'))
BLOCKS_WITH_SKIN.append('audio')
- PREFIX_DICTIONARY['audio'] = '#saudio_'
+ MEDIA_BLOCK2TYPE['audio'] = 'audio'
MEDIA_SHAPES.append('audiosmall')
MEDIA_SHAPES.append('audiooff')
MEDIA_SHAPES.append('audioon')
@@ -170,7 +179,7 @@ boolean operators from Numbers palette'))
default='None',
help_string=_('Sugar Journal video object'))
BLOCKS_WITH_SKIN.append('video')
- PREFIX_DICTIONARY['video'] = '#svideo_'
+ MEDIA_BLOCK2TYPE['video'] = 'video'
MEDIA_SHAPES.append('videosmall')
MEDIA_SHAPES.append('videooff')
MEDIA_SHAPES.append('videoon')
@@ -182,7 +191,7 @@ boolean operators from Numbers palette'))
default='None',
help_string=_('Sugar Journal description field'))
BLOCKS_WITH_SKIN.append('description')
- PREFIX_DICTIONARY['description'] = '#sdescr_'
+ MEDIA_BLOCK2TYPE['description'] = 'descr'
MEDIA_SHAPES.append('descriptionsmall')
MEDIA_SHAPES.append('descriptionoff')
MEDIA_SHAPES.append('descriptionon')
@@ -194,7 +203,6 @@ boolean operators from Numbers palette'))
special_name=_('text'),
help_string=_('string value'))
- primitive_dictionary['show'] = self._prim_show
palette.add_block('show',
style='basic-style-1arg',
label=_('show'),
@@ -204,8 +212,8 @@ boolean operators from Numbers palette'))
help_string=_('draws text or show media from the \
Journal'))
self.tw.lc.def_prim('show', 1,
- lambda self, x:
- primitive_dictionary['show'](x, True))
+ Primitive(self.tw.lc.show,
+ arg_descs=[ArgSlot(TYPE_OBJECT), ConstantArg(True)]))
palette.add_block('showaligned',
hidden=True,
@@ -218,8 +226,8 @@ Journal'))
help_string=_('draws text or show media from the \
Journal'))
self.tw.lc.def_prim('showaligned', 1,
- lambda self, x:
- primitive_dictionary['show'](x, False))
+ Primitive(self.tw.lc.show,
+ arg_descs=[ArgSlot(TYPE_OBJECT), ConstantArg(False)]))
primitive_dictionary['setscale'] = self._prim_setscale
palette.add_block('setscale',
@@ -415,7 +423,6 @@ to the stack'))
self.tw.lc.def_prim('see', 0,
lambda self: primitive_dictionary['see']())
- primitive_dictionary['time'] = self._prim_time
palette.add_block('time',
style='box-style',
label=_('time'),
@@ -424,7 +431,15 @@ to the stack'))
help_string=_('elapsed time (in seconds) since \
program started'))
self.tw.lc.def_prim('time', 0,
- lambda self: primitive_dictionary['time']())
+ Primitive(Primitive.identity,
+ return_type=TYPE_INT,
+ arg_descs=[
+ ConstantArg(Primitive(int, arg_descs=[
+ ConstantArg(Primitive(Primitive.minus, arg_descs=[
+ ConstantArg(Primitive(time)),
+ ConstantArg(Primitive(self.tw.lc.get_start_time))
+ ]))]))],
+ call_afterwards=self.after_time))
def _extras_palette(self):
debug_output('creating %s palette' % _('extras'),
@@ -434,7 +449,6 @@ program started'))
help_string=_('Palette of extra options'),
position=8)
- primitive_dictionary['push'] = self._prim_push
palette.add_block('push',
style='basic-style-1arg',
#TRANS: push adds a new item to the program stack
@@ -444,11 +458,12 @@ program started'))
help_string=_('pushes value onto FILO (first-in \
last-out heap)'))
self.tw.lc.def_prim('push', 1,
- lambda self, x: primitive_dictionary['push'](x))
+ Primitive(self.tw.lc.heap.append,
+ arg_descs=[ArgSlot(TYPE_OBJECT)],
+ call_afterwards=self.after_push))
define_logo_function('tapush', 'to tapush :foo\nmake "taheap fput \
:foo :taheap\nend\nmake "taheap []\n')
- primitive_dictionary['printheap'] = self._prim_printheap
palette.add_block('printheap',
style='basic-style-extended-vertical',
label=_('show heap'),
@@ -457,11 +472,12 @@ last-out heap)'))
help_string=_('shows values in FILO (first-in \
last-out heap)'))
self.tw.lc.def_prim('printheap', 0,
- lambda self: primitive_dictionary['printheap']())
+ Primitive(self.tw.print_,
+ arg_descs=[ConstantArg(Primitive(self.tw.lc.get_heap)),
+ ConstantArg(False)]))
define_logo_function('taprintheap', 'to taprintheap \nprint :taheap\n\
end\n')
- primitive_dictionary['clearheap'] = self._prim_emptyheap
palette.add_block('clearheap',
style='basic-style-extended-vertical',
label=_('empty heap'),
@@ -470,11 +486,10 @@ end\n')
help_string=_('emptys FILO (first-in-last-out \
heap)'))
self.tw.lc.def_prim('clearheap', 0,
- lambda self: primitive_dictionary['clearheap']())
+ Primitive(self.tw.lc.reset_heap, call_afterwards=self.after_pop))
define_logo_function('taclearheap', 'to taclearheap\nmake "taheap []\n\
end\n')
- primitive_dictionary['pop'] = self._prim_pop
palette.add_block('pop',
style='box-style',
#TRANS: pop removes a new item from the program stack
@@ -485,11 +500,11 @@ end\n')
help_string=_('pops value off FILO (first-in \
last-out heap)'))
self.tw.lc.def_prim('pop', 0,
- lambda self: primitive_dictionary['pop']())
+ Primitive(self.tw.lc.heap.pop, return_type=TYPE_BOX,
+ call_afterwards=self.after_pop))
define_logo_function('tapop', 'to tapop\nif emptyp :taheap [stop]\n\
make "tmp first :taheap\nmake "taheap butfirst :taheap\noutput :tmp\nend\n')
- primitive_dictionary['isheapempty'] = self._prim_is_heap_empty
palette.add_block('isheapempty',
hidden=True,
style='box-style',
@@ -498,7 +513,12 @@ make "tmp first :taheap\nmake "taheap butfirst :taheap\noutput :tmp\nend\n')
value_block=True,
help_string=_('returns True if heap is empty'))
self.tw.lc.def_prim('isheapempty', 0,
- lambda self: primitive_dictionary['isheapempty']())
+ Primitive(int, return_type=TYPE_INT,
+ arg_descs=[ConstantArg(
+ Primitive(Primitive.not_, return_type=TYPE_BOOL,
+ arg_descs=[ConstantArg(
+ Primitive(self.tw.lc.get_heap,
+ return_type=TYPE_BOOL))]))]))
primitive_dictionary['saveheap'] = self._prim_save_heap
palette.add_block('saveheap',
@@ -522,7 +542,6 @@ last-out heap) from a file'))
self.tw.lc.def_prim('loadheap', 1,
lambda self, x: primitive_dictionary['loadheap'](x))
- primitive_dictionary['isheapempty2'] = self._prim_is_heap_empty_bool
palette.add_block('isheapempty2',
style='boolean-block-style',
label=_('empty heap?'),
@@ -530,10 +549,12 @@ last-out heap) from a file'))
value_block=True,
help_string=_('returns True if heap is empty'))
self.tw.lc.def_prim('isheapempty2', 0,
- lambda self:
- primitive_dictionary['isheapempty2']())
+ # Python automatically converts the heap to a boolean in contexts
+ # where a boolean is needed
+ Primitive(Primitive.not_, return_type=TYPE_BOOL,
+ arg_descs=[ConstantArg(
+ Primitive(self.tw.lc.get_heap, return_type=TYPE_BOOL))]))
- primitive_dictionary['print'] = self._prim_print
palette.add_block('comment',
style='basic-style-1arg',
label=_('comment'),
@@ -542,8 +563,7 @@ last-out heap) from a file'))
string_or_number=True,
help_string=_('places a comment in your code'))
self.tw.lc.def_prim('comment', 1,
- lambda self, x:
- primitive_dictionary['print'](x, True))
+ Primitive(Primitive.comment, arg_descs=[ArgSlot(TYPE_STRING)]))
palette.add_block('print',
style='basic-style-1arg',
@@ -554,26 +574,28 @@ last-out heap) from a file'))
help_string=_('prints value in status block at \
bottom of the screen'))
self.tw.lc.def_prim('print', 1,
- lambda self, x:
- primitive_dictionary['print'](x, False))
+ Primitive(self.tw.print_,
+ arg_descs=[ArgSlot(TYPE_OBJECT), ConstantArg(False)]))
- primitive_dictionary['chr'] = self._prim_chr
palette.add_block('chr',
style='number-style-1arg',
label='chr',
prim_name='chr',
help_string=_('Python chr operator'))
self.tw.lc.def_prim('chr', 1,
- lambda self, x: primitive_dictionary['chr'](x))
+ Primitive(chr, return_type=TYPE_CHAR,
+ arg_descs=[ArgSlot(TYPE_INT)]))
- primitive_dictionary['int'] = self._prim_int
palette.add_block('int',
style='number-style-1arg',
label='int',
prim_name='int',
help_string=_('Python int operator'))
self.tw.lc.def_prim('int', 1,
- lambda self, x: primitive_dictionary['int'](x))
+ # leave over the actual work to the type system, and just demand
+ # that the argument be converted to an integer
+ Primitive(Primitive.identity, return_type=TYPE_INT,
+ arg_descs=[ArgSlot(TYPE_INT)]))
palette.add_block('polar',
style='basic-style-extended-vertical',
@@ -583,7 +605,6 @@ bottom of the screen'))
self.tw.lc.def_prim('polar', 0,
lambda self: self.tw.set_polar(True))
- primitive_dictionary['myfunction'] = self._prim_myfunction
palette.add_block('myfunc1arg',
style='number-style-var-arg',
label=[_('Python'), 'f(x)', 'x'],
@@ -593,8 +614,8 @@ bottom of the screen'))
help_string=_('a programmable block: used to add \
advanced single-variable math equations, e.g., sin(x)'))
self.tw.lc.def_prim('myfunction', 2,
- lambda self, f, x:
- primitive_dictionary['myfunction'](f, [x]))
+ Primitive(self.tw.lc.prim_myfunction, return_type=TYPE_FLOAT,
+ arg_descs=[ArgSlot(TYPE_STRING), ArgSlot(TYPE_FLOAT)]))
palette.add_block('myfunc2arg',
hidden=True,
@@ -607,8 +628,9 @@ advanced single-variable math equations, e.g., sin(x)'))
help_string=_('a programmable block: used to add \
advanced multi-variable math equations, e.g., sqrt(x*x+y*y)'))
self.tw.lc.def_prim('myfunction2', 3,
- lambda self, f, x, y:
- primitive_dictionary['myfunction'](f, [x, y]))
+ Primitive(self.tw.lc.prim_myfunction, return_type=TYPE_FLOAT,
+ arg_descs=[ArgSlot(TYPE_STRING), ArgSlot(TYPE_FLOAT),
+ ArgSlot(TYPE_FLOAT)]))
palette.add_block('myfunc3arg',
hidden=True,
@@ -621,8 +643,9 @@ advanced multi-variable math equations, e.g., sqrt(x*x+y*y)'))
help_string=_('a programmable block: used to add \
advanced multi-variable math equations, e.g., sin(x+y+z)'))
self.tw.lc.def_prim('myfunction3', 4,
- lambda self, f, x, y, z:
- primitive_dictionary['myfunction'](f, [x, y, z]))
+ Primitive(self.tw.lc.prim_myfunction, return_type=TYPE_FLOAT,
+ arg_descs=[ArgSlot(TYPE_STRING), ArgSlot(TYPE_FLOAT),
+ ArgSlot(TYPE_FLOAT), ArgSlot(TYPE_FLOAT)]))
palette.add_block('cartesian',
style='basic-style-extended-vertical',
@@ -820,23 +843,21 @@ module found in the Journal'))
templates'),
position=9)
- primitive_dictionary['hideblocks'] = self._prim_hideblocks
palette.add_block('hideblocks',
style='basic-style-extended-vertical',
label=_('hide blocks'),
prim_name='hideblocks',
help_string=_('declutters canvas by hiding blocks'))
self.tw.lc.def_prim('hideblocks', 0,
- lambda self: primitive_dictionary['hideblocks']())
+ Primitive(self._prim_hideblocks, export_me=False))
- primitive_dictionary['showblocks'] = self._prim_showblocks
palette.add_block('showblocks',
style='basic-style-extended-vertical',
label=_('show blocks'),
prim_name='showblocks',
help_string=_('restores hidden blocks'))
self.tw.lc.def_prim('showblocks', 0,
- lambda self: primitive_dictionary['showblocks']())
+ Primitive(self._prim_showblocks, export_me=False))
palette.add_block('fullscreen',
style='basic-style-extended-vertical',
@@ -909,7 +930,9 @@ Journal objects'))
prim_name='lpos',
logo_command='lpos',
help_string=_('xcor of left of screen'))
- self.tw.lc.def_prim('lpos', 0, lambda self: CONSTANTS['leftpos'])
+ self.tw.lc.def_prim('lpos', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('leftpos')]))
palette.add_block('bottompos',
style='box-style',
@@ -917,7 +940,9 @@ Journal objects'))
prim_name='bpos',
logo_command='bpos',
help_string=_('ycor of bottom of screen'))
- self.tw.lc.def_prim('bpos', 0, lambda self: CONSTANTS['bottompos'])
+ self.tw.lc.def_prim('bpos', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('bottompos')]))
palette.add_block('width',
style='box-style',
@@ -925,7 +950,9 @@ Journal objects'))
prim_name='hres',
logo_command='width',
help_string=_('the canvas width'))
- self.tw.lc.def_prim('hres', 0, lambda self: CONSTANTS['width'])
+ self.tw.lc.def_prim('hres', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('width')]))
palette.add_block('rightpos',
style='box-style',
@@ -933,7 +960,9 @@ Journal objects'))
prim_name='rpos',
logo_command='rpos',
help_string=_('xcor of right of screen'))
- self.tw.lc.def_prim('rpos', 0, lambda self: CONSTANTS['rightpos'])
+ self.tw.lc.def_prim('rpos', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('rightpos')]))
palette.add_block('toppos',
style='box-style',
@@ -941,7 +970,9 @@ Journal objects'))
prim_name='tpos',
logo_command='tpos',
help_string=_('ycor of top of screen'))
- self.tw.lc.def_prim('tpos', 0, lambda self: CONSTANTS['toppos'])
+ self.tw.lc.def_prim('tpos', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('toppos')]))
palette.add_block('height',
style='box-style',
@@ -949,7 +980,9 @@ Journal objects'))
prim_name='vres',
logo_command='height',
help_string=_('the canvas height'))
- self.tw.lc.def_prim('vres', 0, lambda self: CONSTANTS['height'])
+ self.tw.lc.def_prim('vres', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('height')]))
palette.add_block('titlex',
hidden=True,
@@ -958,7 +991,9 @@ Journal objects'))
label=_('title x'),
logo_command='titlex',
prim_name='titlex')
- self.tw.lc.def_prim('titlex', 0, lambda self: CONSTANTS['titlex'])
+ self.tw.lc.def_prim('titlex', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('titlex')]))
palette.add_block('titley',
hidden=True,
@@ -967,7 +1002,9 @@ Journal objects'))
label=_('title y'),
logo_command='titley',
prim_name='titley')
- self.tw.lc.def_prim('titley', 0, lambda self: CONSTANTS['titley'])
+ self.tw.lc.def_prim('titley', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('titley')]))
palette.add_block('leftx',
hidden=True,
@@ -976,7 +1013,9 @@ Journal objects'))
label=_('left x'),
prim_name='leftx',
logo_command='leftx')
- self.tw.lc.def_prim('leftx', 0, lambda self: CONSTANTS['leftx'])
+ self.tw.lc.def_prim('leftx', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('leftx')]))
palette.add_block('topy',
hidden=True,
@@ -985,7 +1024,9 @@ Journal objects'))
label=_('top y'),
prim_name='topy',
logo_command='topy')
- self.tw.lc.def_prim('topy', 0, lambda self: CONSTANTS['topy'])
+ self.tw.lc.def_prim('topy', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('topy')]))
palette.add_block('rightx',
hidden=True,
@@ -994,7 +1035,9 @@ Journal objects'))
label=_('right x'),
prim_name='rightx',
logo_command='rightx')
- self.tw.lc.def_prim('rightx', 0, lambda self: CONSTANTS['rightx'])
+ self.tw.lc.def_prim('rightx', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('rightx')]))
palette.add_block('bottomy',
hidden=True,
@@ -1003,7 +1046,9 @@ Journal objects'))
label=_('bottom y'),
prim_name='bottomy',
logo_command='bottomy')
- self.tw.lc.def_prim('bottomy', 0, lambda self: CONSTANTS['bottomy'])
+ self.tw.lc.def_prim('bottomy', 0,
+ Primitive(CONSTANTS.get, return_type=TYPE_INT,
+ arg_descs=[ConstantArg('bottomy')]))
def _myblocks_palette(self):
''' User-defined macros are saved as a json-encoded file;
@@ -1190,6 +1235,17 @@ Journal objects'))
else:
return False
+ def after_pop(self):
+ if self.tw.lc.update_values:
+ if not self.tw.lc.heap:
+ self.tw.lc.update_label_value('pop')
+ else:
+ self.tw.lc.update_label_value('pop', self.tw.lc.heap[-1])
+
+ def after_push(self, val):
+ if self.tw.lc.update_values:
+ self.tw.lc.update_label_value('pop', val)
+
def _prim_pop(self):
""" Pop value off of FILO """
if len(self.tw.lc.heap) == 0:
@@ -1604,6 +1660,12 @@ Journal objects'))
self._prim_show(s)
y -= int(self.tw.canvas.textsize * self.tw.lead)
+ def after_time(self, elapsed_time):
+ """ Update the label of the 'time' block after computing the new
+ value. """
+ if self.tw.lc.update_values:
+ self.tw.lc.update_label_value('time', elapsed_time)
+
def _prim_time(self):
""" Number of seconds since program execution has started or
clean (prim_clear) block encountered """
diff --git a/pyexported/window_setup.py b/pyexported/window_setup.py
index 5e9becf..45c7ba4 100644
--- a/pyexported/window_setup.py
+++ b/pyexported/window_setup.py
@@ -1,65 +1,17 @@
#!/usr/bin/env python
-# TODO remove unused imports and global variables
+import cairo
import pygtk
pygtk.require('2.0')
import gtk
import gobject
-from gettext import gettext as _
-
-try:
- import gst
- _GST_AVAILABLE = True
-except ImportError:
- # Turtle Art should not fail if gst is not available
- _GST_AVAILABLE = False
-
import os
-import subprocess
-import errno
from sys import argv
-from random import uniform
-from math import atan2, pi
-DEGTOR = 2 * pi / 360
-
-import locale
-
-from TurtleArt.taconstants import (HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE,
- MEDIA_SHAPES, STATUS_SHAPES, OVERLAY_SHAPES,
- TOOLBAR_SHAPES, TAB_LAYER, RETURN, OVERLAY_LAYER,
- CATEGORY_LAYER, BLOCKS_WITH_SKIN, ICON_SIZE,
- PALETTE_SCALE, PALETTE_WIDTH, SKIN_PATHS, MACROS,
- TOP_LAYER, BLOCK_LAYER, OLD_NAMES, DEFAULT_TURTLE,
- TURTLE_LAYER, EXPANDABLE, NO_IMPORT, TEMPLATES,
- PYTHON_SKIN, PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK,
- EXPANDABLE_ARGS, XO1, XO15, XO175, XO30, XO4, TITLEXY,
- CONTENT_ARGS, CONSTANTS, EXPAND_SKIN, PROTO_LAYER,
- EXPANDABLE_FLOW, SUFFIX)
-from TurtleArt.talogo import (LogoCode, primitive_dictionary, logoerror)
-from TurtleArt.tacanvas import TurtleGraphics
-from TurtleArt.tablock import (Blocks, Block)
-from TurtleArt.taturtle import (Turtles, Turtle)
-from TurtleArt.tautils import (magnitude, get_load_name, get_save_name, data_from_file,
- data_to_file, round_int, get_id, get_pixbuf_from_journal,
- movie_media_type, audio_media_type, image_media_type,
- save_picture, calc_image_size, get_path, hide_button_hit,
- show_button_hit, arithmetic_check, xy,
- find_block_to_run, find_top_block, journal_check,
- find_group, find_blk_below, data_to_string,
- find_start_stack, get_hardware, debug_output,
- error_output, convert, find_bot_block,
- restore_clamp, collapse_clamp, data_from_string,
- increment_name, get_screen_dpi)
-from TurtleArt.tasprite_factory import (SVG, svg_str_to_pixbuf, svg_from_file)
-from TurtleArt.sprites import (Sprites, Sprite)
-
-if _GST_AVAILABLE:
- from TurtleArt.tagplay import stop_media
-
-import cairo
-
+from TurtleArt.tablock import Media
+from TurtleArt.taconstants import CONSTANTS
+from TurtleArt.tatype import *
from TurtleArt.tawindow import TurtleArtWindow
@@ -174,7 +126,7 @@ def get_tw():
# win.set_default_size(gui.width, gui.height)
# win.move(gui.x, gui.y)
win.maximize()
- # win.set_title('%s %s' % (gui.name, str(gui.version)))
+ win.set_title(str(gui.name))
# if os.path.exists(os.path.join(gui._execdirname, gui._ICON_SUBPATH)):
# win.set_icon_from_file(os.path.join(gui._execdirname,
# gui._ICON_SUBPATH))
diff --git a/util/codegen.py b/util/codegen.py
index ead6dd0..3785085 100644
--- a/util/codegen.py
+++ b/util/codegen.py
@@ -35,6 +35,7 @@
Modified by Marion Zepf.
"""
from ast import *
+from ast_extensions import Comment
def to_source(node, indent_with=' ' * 4, add_line_information=False):
@@ -67,7 +68,7 @@ class SourceGenerator(NodeVisitor):
"""
UNARYOP_SYMBOLS = {Invert: "~", Not: "not", UAdd: "+", USub: "-"}
- # TODO avoid turning (-1)**2 into -1**2
+ # TODO use parentheses around expressions only where necessary
BINOP_SYMBOLS = {Add: "+", Sub: "-", Mult: "*", Div: "/", Mod: "%",
LShift: "<<", RShift:">>", BitOr: "|", BitXor: "^",
BitAnd: "&", FloorDiv: "//", Pow: "**"}
@@ -219,7 +220,6 @@ class SourceGenerator(NodeVisitor):
paren_or_comma()
self.write('**')
self.visit(node.kwargs)
- # TODO wtf???
self.write(have_args and '):' or ':')
self.body(node.body)
@@ -326,8 +326,10 @@ class SourceGenerator(NodeVisitor):
def visit_Return(self, node):
self.newline(node)
- self.write('return ')
- self.visit(node.value)
+ self.write('return')
+ if hasattr(node, "value") and node.value is not None:
+ self.write(' ')
+ self.visit(node.value)
def visit_Break(self, node):
self.newline(node)
@@ -356,6 +358,10 @@ class SourceGenerator(NodeVisitor):
self.write(', ')
self.visit(node.tback)
+ def visit_Comment(self, node):
+ self.newline(node)
+ self.write('#' + str(node.text))
+
# Expressions
def visit_Attribute(self, node):
@@ -388,9 +394,11 @@ class SourceGenerator(NodeVisitor):
self.write('**')
self.visit(node.kwargs)
self.write(')')
+ visit_TypedCall = visit_Call
def visit_Name(self, node):
self.write(node.id)
+ visit_TypedName = visit_Name
def visit_Str(self, node):
self.write(repr(node.s))
@@ -408,7 +416,6 @@ class SourceGenerator(NodeVisitor):
if idx:
self.write(', ')
self.visit(item)
- # TODO wtf???
self.write(idx and ')' or ',)')
def sequence_visit(left, right):
@@ -470,6 +477,10 @@ class SourceGenerator(NodeVisitor):
self.write('[')
self.visit(node.slice)
self.write(']')
+ visit_TypedSubscript = visit_Subscript
+
+ def visit_Index(self, node):
+ self.visit(node.value)
def visit_Slice(self, node):
if node.lower is not None:
@@ -493,10 +504,21 @@ class SourceGenerator(NodeVisitor):
self.visit(node.value)
def visit_Lambda(self, node):
- self.write('lambda ')
+ self.write('(lambda ')
self.signature(node.args)
self.write(': ')
self.visit(node.body)
+ self.write(')')
+
+ def visit_LambdaWithStrBody(self, node):
+ self.write('(lambda ')
+ for idx, arg in enumerate(node.args):
+ if idx:
+ self.write(', ')
+ self.visit(arg)
+ self.write(': ')
+ self.write(node.body_str)
+ self.write(')')
def visit_Ellipsis(self, node):
self.write('Ellipsis')