Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/doc
diff options
context:
space:
mode:
authorMarion Zepf <marion.zepf@gmail.com>2013-10-29 21:25:26 (GMT)
committer Walter Bender <walter@sugarlabs.org>2013-10-29 21:25:26 (GMT)
commitdb8c29ce3204b79aed7b9679c91f7abf3f6f2102 (patch)
tree287d0eed75c47669ab27ec708b0b2c24f7390d3b /doc
parent671ee68af425063395e9f2248f93bb723b158406 (diff)
convert to type branch of python export code
Diffstat (limited to 'doc')
-rw-r--r--doc/primitives-with-arguments.md125
-rw-r--r--doc/type-system.md116
2 files changed, 241 insertions, 0 deletions
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.