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