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 --- (limited to 'doc') 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. -- cgit v0.9.1