From db8c29ce3204b79aed7b9679c91f7abf3f6f2102 Mon Sep 17 00:00:00 2001 From: Marion Zepf Date: Tue, 29 Oct 2013 21:25:26 +0000 Subject: convert to type branch of python export code --- 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__ == '' 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) == '': + 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') -- cgit v0.9.1