Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarion Zepf <marion.zepf@gmail.com>2013-10-27 02:08:57 (GMT)
committer Walter Bender <walter@sugarlabs.org>2013-10-27 02:08:57 (GMT)
commit2ce8a7b3da346ad20dda2fe13522d7dfba1286ce (patch)
tree366c1f514c83aa0658679b1c9a56a3d0a5214b9f
parent629722d8859d1e8db47417673cb78963d41ce435 (diff)
python export
-rw-r--r--TurtleArt/taprimitive.py978
-rw-r--r--pyexported/__init__.py0
-rw-r--r--pyexported/window_setup.py186
-rw-r--r--util/codegen.py571
4 files changed, 1735 insertions, 0 deletions
diff --git a/TurtleArt/taprimitive.py b/TurtleArt/taprimitive.py
new file mode 100644
index 0000000..bac9f7b
--- /dev/null
+++ b/TurtleArt/taprimitive.py
@@ -0,0 +1,978 @@
+#Copyright (c) 2013 Marion Zepf
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import ast
+from gettext import gettext as _
+
+#from ast_pprint import * # only used for debugging, safe to comment out
+
+from tacanvas import TurtleGraphics
+from taconstants import (Color, CONSTANTS)
+from talogo import LogoCode
+from taturtle import (Turtle, Turtles)
+from tautils import debug_output
+from tawindow import (global_objects, TurtleArtWindow)
+
+
+class PyExportError(BaseException):
+ """ Error that is raised when something goes wrong while converting the
+ blocks to python code """
+
+ def __init__(self, message, block=None):
+ """ message -- the error message
+ block -- the block where the error occurred """
+ self.message = message
+ self.block = block
+
+ def __str__(self):
+ if self.block is not None:
+ return _("error in highlighted block") + ": " + str(self.message)
+ else:
+ return _("error") + ": " + str(self.message)
+
+
+class Primitive(object):
+ """ Something that can be called when the block code is executed in TA,
+ but that can also be transformed into a Python AST.
+ """
+
+ STANDARD_OPERATORS = {'plus': (ast.UAdd, ast.Add),
+ 'minus': (ast.USub, ast.Sub),
+ 'multiply': ast.Mult,
+ 'divide': ast.Div,
+ 'modulo': ast.Mod,
+ 'power': ast.Pow,
+ 'integer_division': ast.FloorDiv,
+ 'bitwise_and': ast.BitAnd,
+ 'bitwise_or': ast.BitOr,
+ 'and_': ast.And,
+ 'or_': ast.Or,
+ 'not_': ast.Not,
+ 'equals': ast.Eq,
+ '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.
+ call_afterwards -- Code to call after this Primitive has been called
+ (e.g., for updating labels in LogoCode) (not used for creating
+ AST)
+ call_me -- True if this Primitive should be called (default), False
+ if it should be passed on as a Primitive object
+ export_me -- True iff this Primitive should be exported to Python
+ code (the default case) """
+ self.func = func
+
+ if constant_args is None:
+ self.constant_args = {}
+ else:
+ self.constant_args = constant_args
+
+ if slot_wrappers is None:
+ self.slot_wrappers = {}
+ else:
+ # check for duplicate argument indices
+ msg = ("argument at index %d is associated with multiple slot "
+ "wrappers")
+ nums = set()
+ tuples = []
+ for k in slot_wrappers.keys():
+ if isinstance(k, int):
+ nums.add(k)
+ elif isinstance(k, tuple):
+ tuples.append(k)
+ tuples.sort()
+ prev_tuple = (0, 0)
+ for tuple_ in tuples:
+ if prev_tuple[1] > tuple_[0]:
+ raise KeyError(msg % (tuple_[0]))
+ for i in range(*tuple_):
+ if i in nums:
+ raise KeyError(msg % (i))
+ prev_tuple = tuple_
+ self.slot_wrappers = slot_wrappers
+
+ self.call_afterwards = call_afterwards
+ self.call_me = call_me
+ self.export_me = export_me
+
+ def __repr__(self):
+ return "Primitive(" + repr(self.func) + ")"
+
+ def _apply_wrappers(self, runtime_args, runtime_kwargs,
+ convert_to_ast=False):
+ """ Apply the slot wrappers """
+ # make a map from the start indices of all ranges to their ends
+ range_ends = {}
+ for range_tuple in sorted(self.slot_wrappers.keys()):
+ if isinstance(range_tuple, tuple):
+ (start, end) = range_tuple
+ range_ends[start] = end
+
+ new_args = []
+ i = 0
+ while i < len(runtime_args):
+ arg = runtime_args[i]
+ wrapper = self.slot_wrappers.get(i)
+ if wrapper is None:
+ (start, end) = (i, range_ends.get(i))
+ if end is None:
+ # no slot wrapper found
+ # convert to AST, but don't call
+ if convert_to_ast and isinstance(arg, Primitive):
+ new_args.append(arg.get_ast())
+ else:
+ new_args.append(arg)
+ i += 1
+ else:
+ # range -> slot wrapper around a range of arguments
+ wrapper = self.slot_wrappers.get((start, end))
+ args_for_wrapper = runtime_args[start:end]
+ if not convert_to_ast and call_me(wrapper):
+ wrapper_output = wrapper(*args_for_wrapper)
+ elif convert_to_ast and export_me(wrapper):
+ wrapper_output = value_to_ast(wrapper,
+ *args_for_wrapper)
+ else:
+ # 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)
+ 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_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
+
+ return (new_args, new_kwargs)
+
+ def _add_constant_args(self, runtime_args, runtime_kwargs,
+ convert_to_ast=False):
+ """ Add the constant args and kwargs to the given runtime args and
+ kwargs. Return a list containing all args and a dictionary with all
+ kwargs.
+ convert_to_ast -- convert all constant arguments to ASTs? """
+ all_args = []
+ all_kwargs = runtime_kwargs.copy()
+
+ # args
+ i = 0
+ def _insert_c_args(i):
+ while i in self.constant_args:
+ c_arg = self.constant_args[i]
+ if not convert_to_ast and call_me(c_arg):
+ all_args.append(c_arg())
+ elif convert_to_ast:
+ if export_me(c_arg):
+ all_args.append(value_to_ast(c_arg))
+ else:
+ all_args.append(c_arg)
+ i += 1
+ return i
+ for arg in runtime_args:
+ i = _insert_c_args(i)
+ all_args.append(arg)
+ i += 1
+ i = _insert_c_args(i)
+
+ # kwargs
+ for (key, value) in self.constant_args.iteritems():
+ if isinstance(key, basestring):
+ if not convert_to_ast and call_me(value):
+ all_kwargs[key] = value()
+ elif convert_to_ast:
+ if export_me(value):
+ all_kwargs[key] = value_to_ast(value)
+ else:
+ all_kwargs[key] = value
+
+ return (all_args, all_kwargs)
+
+ def __call__(self, *runtime_args, **runtime_kwargs):
+ """ Execute the function, passing it the arguments received at
+ runtime. Also call the function in self.call_afterwards and pass it
+ all runtime_args and runtime_kwargs.
+ If the very first argument is a LogoCode instance, it may be
+ replaced with the active turtle, the canvas, or nothing (depending
+ on what this primitive wants as its first arg). This argument is
+ also exempt from the slot wrappers. """
+
+ # 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)
+
+ # 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)
+
+ # execute the actual function
+ if first_arg is None or is_bound_instancemethod(self.func):
+ return_value = self.func(*new_args, **new_kwargs)
+ else:
+ return_value = self.func(first_arg, *new_args, **new_kwargs)
+
+ if self.call_afterwards is not None:
+ self.call_afterwards(*new_args, **new_kwargs)
+
+ return return_value
+
+ def get_ast(self, *arg_asts, **kwarg_asts):
+ """ Transform this object into a Python AST. When serialized and
+ executed, the AST will do exactly the same as calling this object. """
+
+ # constant arguments
+ (all_arg_asts, all_kwarg_asts) = self._add_constant_args(arg_asts,
+ kwarg_asts, convert_to_ast=True)
+
+ # slot wrappers
+ (new_arg_asts, new_kwarg_asts) = self._apply_wrappers(all_arg_asts,
+ all_kwarg_asts,
+ convert_to_ast=True)
+
+ # SPECIAL HANDLING #
+
+ # loops
+ if self == LogoCode.prim_loop:
+ controller = self._get_loop_controller()
+ if controller == Primitive.controller_repeat:
+ # 'repeat' loop
+ num_repetitions = new_arg_asts[0]
+ if num_repetitions.func.id == 'controller_repeat':
+ num_repetitions = num_repetitions.args[0]
+ repeat_iter = get_call_ast("range", [num_repetitions])
+ # TODO use new variable name in nested loops
+ loop_ast = ast.For(target=ast.Name(id="i", ctx=ast.Store),
+ iter=repeat_iter,
+ body=new_arg_asts[1],
+ orelse=[])
+ return loop_ast
+ else:
+ if controller == Primitive.controller_forever:
+ condition_ast = ast.Name(id="True", ctx=ast.Load)
+ elif controller == Primitive.controller_while:
+ condition_ast = new_arg_asts[0].args[0]
+ elif controller == Primitive.controller_until:
+ condition_ast = ast.UnaryOp(op=ast.Not,
+ operand=new_arg_asts[0].args[0])
+ else:
+ raise ValueError("unknown loop controller: " +
+ repr(controller))
+ loop_ast = ast.While(test=condition_ast,
+ body=new_arg_asts[1],
+ orelse=[])
+ return loop_ast
+
+ # conditionals
+ elif self in (LogoCode.prim_if, LogoCode.prim_ifelse):
+ test = new_arg_asts[0]
+ body = new_arg_asts[1]
+ if len(new_arg_asts) > 2:
+ orelse = new_arg_asts[2]
+ else:
+ orelse = []
+ if_ast = ast.If(test=test, body=body, orelse=orelse)
+ return if_ast
+
+ # 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
+ 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)
+
+ # 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])
+
+ # 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'
+ if len(new_arg_asts) == 1:
+ if isinstance(op, tuple):
+ op = op[0]
+ return ast.UnaryOp(op=op, operand=new_arg_asts[0])
+ elif len(new_arg_asts) == 2:
+ if isinstance(op, tuple):
+ op = op[1]
+ (left, right) = new_arg_asts
+ if issubclass(op, ast.boolop):
+ return ast.BoolOp(op=op, values=[left, right])
+ elif issubclass(op, ast.cmpop):
+ return ast.Compare(left=left, ops=[op],
+ 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)
+
+ # identity
+ elif self == Primitive.identity:
+ if len(new_arg_asts) == 1:
+ return new_arg_asts[0]
+ 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
+
+ # 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)
+
+ def __eq__(self, other):
+ """ Two Primitives are equal iff their all their properties are equal.
+ Consider bound and unbound methods equal. """
+ # other is a Primitive
+ if isinstance(other, Primitive):
+ return (self == other.func and
+ self.constant_args == other.constant_args and
+ self.slot_wrappers == other.slot_wrappers and
+ self.call_afterwards == other.call_afterwards and
+ self.export_me == other.export_me)
+
+ # other is a callable
+ elif callable(other):
+ if is_instancemethod(self.func) != is_instancemethod(other):
+ return False
+ elif is_instancemethod(self.func): # and is_instancemethod(other):
+ return (self.func.im_class == other.im_class and
+ self.func.im_func == other.im_func)
+ else:
+ return self.func == other
+
+ elif is_staticmethod(other):
+ return self.func == other.__func__
+
+ # other is neither a Primitive nor a callable
+ else:
+ return False
+
+ def wants_turtle(self):
+ """ Does this Primitive want to get the active turtle as its first
+ argument? """
+ return self._wants(Turtle)
+
+ def wants_turtles(self):
+ """ Does this Primitive want to get the Turtles instance as its
+ first argument? """
+ return self._wants(Turtles)
+
+ def wants_canvas(self):
+ """ Does this Primitive want to get the canvas as its first
+ argument? """
+ return self._wants(TurtleGraphics)
+
+ def wants_logocode(self):
+ """ Does this Primitive want to get the LogoCode instance as its
+ first argument? """
+ return self._wants(LogoCode)
+
+ def wants_tawindow(self):
+ """ Does this Primitive want to get the TurtleArtWindow instance
+ as its first argument? """
+ return self._wants(TurtleArtWindow)
+
+ def wants_nothing(self):
+ """ Does this Primitive want nothing as its first argument? I.e. does
+ it want to be passed all the arguments of the block and nothing
+ else? """
+ return not is_instancemethod(self.func)
+
+ def _wants(self, theClass):
+ if is_instancemethod(self.func):
+ return self.func.im_class == theClass
+ else:
+ return False
+
+ # treat the following methods in a special way when converting the
+ # Primitive to an AST
+
+ @staticmethod
+ def make_tuple(*values):
+ """ This method corresponds to a Python tuple consisting of the given
+ values. """
+ return tuple(values)
+
+ @staticmethod
+ def controller_repeat(num):
+ """ Loop controller for the 'repeat' block """
+ for i in range(num):
+ yield True
+ yield False
+
+ @staticmethod
+ def controller_forever():
+ """ Loop controller for the 'forever' block """
+ while True:
+ yield True
+
+ @staticmethod
+ def controller_while(boolean):
+ """ Loop controller for the 'while' block """
+ while boolean:
+ yield True
+ yield False
+
+ @staticmethod
+ def controller_until(boolean):
+ """ Loop controller for the 'until' block """
+ while not boolean:
+ yield True
+ yield False
+
+ LOOP_CONTROLLERS = [controller_repeat, controller_forever,
+ controller_while, controller_until]
+
+ def _get_loop_controller(self):
+ """ Return the controller for this loop Primitive. Raise a
+ ValueError if no controller was found. """
+ def _is_loop_controller(candidate):
+ return (callable(candidate)
+ and candidate in Primitive.LOOP_CONTROLLERS)
+
+ # look at the first constant argument
+ first_const = self.constant_args.get(0, None)
+ if _is_loop_controller(first_const):
+ return first_const
+
+ # look at the first slot wrapper
+ first_wrapper = self.slot_wrappers.get(0, None)
+ if _is_loop_controller(first_wrapper):
+ return first_wrapper
+
+ # no controller found
+ raise ValueError("found no loop controller for " + repr(self))
+
+ @staticmethod
+ def do_nothing():
+ pass
+
+ @staticmethod
+ def identity(arg):
+ """ Return the argument unchanged """
+ return arg
+
+ @staticmethod
+ def group(prim_list):
+ """ Group together multiple Primitives into one. Treat each Primitive
+ as a separate line of code. """
+ return_val = None
+ for prim in prim_list:
+ return_val = prim()
+ return return_val
+
+ @staticmethod
+ def 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
+ argument is a tuple of length 2 and the second is None, use the
+ values in the tuple as arg1 and arg2. """
+ if isinstance(arg1, (list, tuple)) and len(arg1) == 2 and arg2 is None:
+ (arg1, arg2) = arg1
+ if arg2 is None:
+ return + arg1
+ else:
+ 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. """
+ if arg2 is None:
+ return - arg1
+ else:
+ return arg1 - arg2
+
+ @staticmethod
+ def multiply(arg1, arg2):
+ """ Multiply the two arguments """
+ return arg1 * arg2
+
+ @staticmethod
+ def divide(arg1, arg2):
+ """ Divide the first argument by the second """
+ return arg1 / arg2
+
+ @staticmethod
+ def modulo(arg1, arg2):
+ """ Return the remainder of dividing the first argument by the second.
+ If the first argument is a string, format it with the value(s) in
+ the second argument. """
+ return arg1 % arg2
+
+ @staticmethod
+ def power(arg1, arg2):
+ """ Raise the first argument to the power given by the second """
+ return arg1 ** arg2
+
+ @staticmethod
+ def integer_division(arg1, arg2):
+ """ Divide the first argument by the second and return the integer
+ that is smaller than or equal to the result """
+ return arg1 // arg2
+
+ @staticmethod
+ def bitwise_and(arg1, arg2):
+ """ Return the bitwise AND of the two arguments """
+ return arg1 & arg2
+
+ @staticmethod
+ def bitwise_or(arg1, arg2):
+ """ Return the bitwise OR of the two arguments """
+ return arg1 | arg2
+
+ @staticmethod
+ def and_(arg1, arg2):
+ """ Logcially conjoin the two arguments (using short-circuting) """
+ return arg1 and arg2
+
+ @staticmethod
+ def or_(arg1, arg2):
+ """ Logically disjoin the two arguments (using short-circuting) """
+ return arg1 or arg2
+
+ @staticmethod
+ def not_(arg):
+ """ Return True if the argument evaluates to False, and False
+ otherwise. """
+ return not arg
+
+ @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
+
+ @staticmethod
+ def less(arg1, arg2):
+ """ Return arg1 < arg2 """
+ return arg1 < arg2
+
+ @staticmethod
+ def greater(arg1, arg2):
+ """ Return arg1 > arg2 """
+ return arg1 > arg2
+
+
+
+def is_instancemethod(method):
+ # TODO how to access the type `instancemethod` directly?
+ return type(method).__name__ == "instancemethod"
+
+def is_bound_instancemethod(method):
+ return is_instancemethod(method) and method.im_self is not None
+
+def is_unbound_instancemethod(method):
+ return is_instancemethod(method) and method.im_self is None
+
+def is_staticmethod(method):
+ # TODO how to access the type `staticmethod` directly?
+ return type(method).__name__ == "staticmethod"
+
+
+def value_to_ast(value, *args_for_prim, **kwargs_for_prim):
+ """ Turn a value into an AST. Supported types: Primitive, int, float,
+ bool, basestring, list
+ If the value is already an AST, return it unchanged.
+ If the value is a non-exportable Primitive, return None. """
+ # TODO media
+ if isinstance(value, ast.AST):
+ return value
+ elif isinstance(value, Primitive):
+ if value.export_me:
+ return value.get_ast(*args_for_prim, **kwargs_for_prim)
+ else:
+ return None
+ elif isinstance(value, bool):
+ return ast.Name(id=str(value), ctx=ast.Load)
+ elif isinstance(value, (int, float)):
+ return ast.Num(n=value)
+ elif isinstance(value, basestring):
+ return ast.Str(value)
+ elif isinstance(value, list):
+ ast_list = []
+ for item in value:
+ item_ast = value_to_ast(item)
+ if item_ast is not None:
+ ast_list.append(item_ast)
+ return ast.List(elts=ast_list, ctx=ast.Load)
+ 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
+ else:
+ return None
+
+
+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 export_me(something):
+ """ Return True iff this is not a Primitive or its export_me attribute
+ is True, i.e. everything is exportable except for Primitives with
+ export_me == False """
+ return not isinstance(something, Primitive) or something.export_me
+
+
diff --git a/pyexported/__init__.py b/pyexported/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pyexported/__init__.py
diff --git a/pyexported/window_setup.py b/pyexported/window_setup.py
new file mode 100644
index 0000000..5e9becf
--- /dev/null
+++ b/pyexported/window_setup.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+
+# TODO remove unused imports and global variables
+import pygtk
+pygtk.require('2.0')
+import gtk
+import gobject
+
+from gettext import gettext as _
+
+try:
+ import gst
+ _GST_AVAILABLE = True
+except ImportError:
+ # Turtle Art should not fail if gst is not available
+ _GST_AVAILABLE = False
+
+import os
+import subprocess
+import errno
+from sys import argv
+
+from random import uniform
+from math import atan2, pi
+DEGTOR = 2 * pi / 360
+
+import locale
+
+from TurtleArt.taconstants import (HORIZONTAL_PALETTE, VERTICAL_PALETTE, BLOCK_SCALE,
+ MEDIA_SHAPES, STATUS_SHAPES, OVERLAY_SHAPES,
+ TOOLBAR_SHAPES, TAB_LAYER, RETURN, OVERLAY_LAYER,
+ CATEGORY_LAYER, BLOCKS_WITH_SKIN, ICON_SIZE,
+ PALETTE_SCALE, PALETTE_WIDTH, SKIN_PATHS, MACROS,
+ TOP_LAYER, BLOCK_LAYER, OLD_NAMES, DEFAULT_TURTLE,
+ TURTLE_LAYER, EXPANDABLE, NO_IMPORT, TEMPLATES,
+ PYTHON_SKIN, PALETTE_HEIGHT, STATUS_LAYER, OLD_DOCK,
+ EXPANDABLE_ARGS, XO1, XO15, XO175, XO30, XO4, TITLEXY,
+ CONTENT_ARGS, CONSTANTS, EXPAND_SKIN, PROTO_LAYER,
+ EXPANDABLE_FLOW, SUFFIX)
+from TurtleArt.talogo import (LogoCode, primitive_dictionary, logoerror)
+from TurtleArt.tacanvas import TurtleGraphics
+from TurtleArt.tablock import (Blocks, Block)
+from TurtleArt.taturtle import (Turtles, Turtle)
+from TurtleArt.tautils import (magnitude, get_load_name, get_save_name, data_from_file,
+ data_to_file, round_int, get_id, get_pixbuf_from_journal,
+ movie_media_type, audio_media_type, image_media_type,
+ save_picture, calc_image_size, get_path, hide_button_hit,
+ show_button_hit, arithmetic_check, xy,
+ find_block_to_run, find_top_block, journal_check,
+ find_group, find_blk_below, data_to_string,
+ find_start_stack, get_hardware, debug_output,
+ error_output, convert, find_bot_block,
+ restore_clamp, collapse_clamp, data_from_string,
+ increment_name, get_screen_dpi)
+from TurtleArt.tasprite_factory import (SVG, svg_str_to_pixbuf, svg_from_file)
+from TurtleArt.sprites import (Sprites, Sprite)
+
+if _GST_AVAILABLE:
+ from TurtleArt.tagplay import stop_media
+
+import cairo
+
+from TurtleArt.tawindow import TurtleArtWindow
+
+
+# path to the toplevel directory of the TA installation
+_TA_INSTALLATION_PATH = None
+# search the PYTHONPATH for a dir containing TurtleArt/tawindow.py
+PYTHONPATH = os.environ["PYTHONPATH"]
+for path in PYTHONPATH.split(":"):
+ try:
+ entries = os.listdir(path)
+ except OSError:
+ continue
+ if "TurtleArt" in entries:
+ new_path = os.path.join(path, "TurtleArt")
+ try:
+ new_entries = os.listdir(new_path)
+ except OSError:
+ continue
+ if "tawindow.py" in new_entries:
+ _TA_INSTALLATION_PATH = path
+ break
+# if the TA installation path was not found, notify the user and refuse to run
+if _TA_INSTALLATION_PATH is None:
+ print _("The path to the TurtleArt installation must be listed in the "
+ "environment variable PYTHONPATH.")
+ exit(1)
+
+_PLUGIN_SUBPATH = 'plugins'
+_MACROS_SUBPATH = 'macros'
+
+
+
+class DummyTurtleMain(object):
+ """Keep the main objects for running a dummy TA window in one place.
+ (Try not to have to inherit from turtleblocks.TurtleMain.)
+ """
+
+ def __init__(self, win, name="exported project"):
+ """Create a scrolled window to contain the turtle canvas.
+ win -- a GTK toplevel window
+ """
+ self.win = win
+ self.set_title = self.win.set_title
+
+ # setup a scrolled container for the canvas
+ self.vbox = gtk.VBox(False, 0)
+ self.vbox.show()
+ self.sw = gtk.ScrolledWindow()
+ self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.sw.show()
+ self.canvas = gtk.DrawingArea()
+ width = gtk.gdk.screen_width() * 2
+ height = gtk.gdk.screen_height() * 2
+ self.canvas.set_size_request(width, height)
+ self.sw.add_with_viewport(self.canvas)
+ self.canvas.show()
+ self.vbox.pack_end(self.sw, True, True)
+ self.win.add(self.vbox)
+ self.win.show_all()
+
+ # exported code is always in interactive mode
+ interactive = True
+
+ # copied from turtleblocks.TurtleMain._build_window()
+ if interactive:
+ gdk_win = self.canvas.get_window()
+ cr = gdk_win.cairo_create()
+ surface = cr.get_target()
+ else:
+ img_surface = cairo.ImageSurface(cairo.FORMAT_RGB24,
+ 1024, 768)
+ cr = cairo.Context(img_surface)
+ surface = cr.get_target()
+ self.turtle_canvas = surface.create_similar(
+ cairo.CONTENT_COLOR, max(1024, gtk.gdk.screen_width() * 2),
+ max(768, gtk.gdk.screen_height() * 2))
+
+
+
+ # instantiate an instance of a dummy sub-class that supports only
+ # the stuff TurtleGraphics needs
+ # TODO don't hardcode running_sugar
+ self.tw = TurtleArtWindow(self.canvas, _TA_INSTALLATION_PATH,
+ turtle_canvas=self.turtle_canvas,
+ parent=self, running_sugar=False,
+ running_turtleart=False)
+
+ self.name = name
+
+
+ def _quit_ta(self, widget=None, e=None):
+ """Quit all plugins and the main window. No need to prompt the user
+ to save their work, since they cannot change anything.
+ """
+ for plugin in self.tw.turtleart_plugins:
+ if hasattr(plugin, 'quit'):
+ plugin.quit()
+ gtk.main_quit()
+ exit()
+
+
+
+def get_tw():
+ """ Create a GTK window and instantiate a DummyTurtleMain instance. Return
+ the TurtleArtWindow object that holds the turtles and the canvas.
+ """
+ # copied from turtleblocks.TurtleMain._setup_gtk()
+
+ win = gtk.Window(gtk.WINDOW_TOPLEVEL)
+ gui = DummyTurtleMain(win=win, name=argv[0])
+ # TODO re-enable this code (after giving gui the right attributes)
+ # win.set_default_size(gui.width, gui.height)
+ # win.move(gui.x, gui.y)
+ win.maximize()
+ # win.set_title('%s %s' % (gui.name, str(gui.version)))
+ # if os.path.exists(os.path.join(gui._execdirname, gui._ICON_SUBPATH)):
+ # win.set_icon_from_file(os.path.join(gui._execdirname,
+ # gui._ICON_SUBPATH))
+ win.show()
+ win.connect('delete_event', gui._quit_ta)
+
+ return gui.tw
+
+
diff --git a/util/codegen.py b/util/codegen.py
new file mode 100644
index 0000000..ead6dd0
--- /dev/null
+++ b/util/codegen.py
@@ -0,0 +1,571 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2008, Armin Ronacher
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+"""
+ codegen
+ ~~~~~~~
+
+ Extension to ast that allow ast -> python code generation.
+
+ :copyright: Copyright 2008 by Armin Ronacher.
+ :license: BSD.
+
+ Modified by Marion Zepf.
+"""
+from ast import *
+
+
+def to_source(node, indent_with=' ' * 4, add_line_information=False):
+ """This function can convert a node tree back into python sourcecode.
+ This is useful for debugging purposes, especially if you're dealing with
+ custom asts not generated by python itself.
+
+ It could be that the sourcecode is evaluable when the AST itself is not
+ compilable / evaluable. The reason for this is that the AST contains some
+ more data than regular sourcecode does, which is dropped during
+ conversion.
+
+ Each level of indentation is replaced with `indent_with`. Per default this
+ parameter is equal to four spaces as suggested by PEP 8, but it might be
+ adjusted to match the application's styleguide.
+
+ If `add_line_information` is set to `True` comments for the line numbers
+ of the nodes are added to the output. This can be used to spot wrong line
+ number information of statement nodes.
+ """
+ generator = SourceGenerator(indent_with, add_line_information)
+ generator.visit(node)
+ return ''.join(generator.result)
+
+
+class SourceGenerator(NodeVisitor):
+ """This visitor is able to transform a well formed syntax tree into python
+ sourcecode. For more details have a look at the docstring of the
+ `node_to_source` function.
+ """
+
+ UNARYOP_SYMBOLS = {Invert: "~", Not: "not", UAdd: "+", USub: "-"}
+ # TODO avoid turning (-1)**2 into -1**2
+ BINOP_SYMBOLS = {Add: "+", Sub: "-", Mult: "*", Div: "/", Mod: "%",
+ LShift: "<<", RShift:">>", BitOr: "|", BitXor: "^",
+ BitAnd: "&", FloorDiv: "//", Pow: "**"}
+ BOOLOP_SYMBOLS = {And: "and", Or: "or"}
+ CMPOP_SYMBOLS = {Eq: "==", NotEq: "!=", Lt: "<", LtE: "<=", Gt: ">",
+ GtE: ">=", Is: "is", IsNot: "is not", In: "in",
+ NotIn: "not in"}
+
+ def __init__(self, indent_with, add_line_information=False):
+ self.result = []
+ self.indent_with = indent_with
+ self.add_line_information = add_line_information
+ self.indentation = 0
+ self.new_lines = 0
+
+ def write(self, x):
+ if self.new_lines:
+ if self.result:
+ self.result.append('\n' * self.new_lines)
+ self.result.append(self.indent_with * self.indentation)
+ self.new_lines = 0
+ self.result.append(x)
+
+ def newline(self, node=None, extra=0):
+ self.new_lines = max(self.new_lines, 1 + extra)
+ if node is not None and self.add_line_information:
+ self.write('# line: %s' % node.lineno)
+ self.new_lines = 1
+
+ def body(self, statements, do_indent=True):
+ if do_indent:
+ self.indentation += 1
+ for stmt in statements:
+ self.newline()
+ self.visit(stmt)
+ if do_indent:
+ self.indentation -= 1
+
+ def body_or_else(self, node):
+ self.body(node.body)
+ if node.orelse:
+ self.newline()
+ self.write('else:')
+ self.body(node.orelse)
+
+ def signature(self, node):
+ want_comma = []
+ def write_comma():
+ if want_comma:
+ self.write(', ')
+ else:
+ want_comma.append(True)
+
+ padding = [None] * (len(node.args) - len(node.defaults))
+ for arg, default in zip(node.args, padding + node.defaults):
+ write_comma()
+ self.visit(arg)
+ if default is not None:
+ self.write('=')
+ self.visit(default)
+ if node.vararg is not None:
+ write_comma()
+ self.write('*' + node.vararg)
+ if node.kwarg is not None:
+ write_comma()
+ self.write('**' + node.kwarg)
+
+ def decorators(self, node):
+ for decorator in node.decorator_list:
+ self.newline(decorator)
+ self.write('@')
+ self.visit(decorator)
+
+ # Statements
+
+ def visit_Assign(self, node):
+ self.newline(node)
+ for idx, target in enumerate(node.targets):
+ if idx:
+ self.write(', ')
+ self.visit(target)
+ self.write(' = ')
+ self.visit(node.value)
+
+ def visit_AugAssign(self, node):
+ self.newline(node)
+ self.visit(node.target)
+ self.write(self.BINOP_SYMBOLS[node.op] + '=')
+ self.visit(node.value)
+
+ def visit_ImportFrom(self, node):
+ self.newline(node)
+ self.write('from %s%s import ' % ('.' * node.level, node.module))
+ for idx, item in enumerate(node.names):
+ if idx:
+ self.write(', ')
+ self.visit(item)
+
+ def visit_Import(self, node):
+ self.newline(node)
+ for item in node.names:
+ self.write('import ')
+ self.visit(item)
+
+ def visit_Expr(self, node):
+ self.newline(node)
+ self.generic_visit(node)
+
+ def visit_Module(self, node):
+ self.body(node.body, do_indent=False)
+
+ def visit_FunctionDef(self, node):
+ self.newline(extra=1)
+ self.decorators(node)
+ self.newline(node)
+ self.write('def %s(' % node.name)
+ self.signature(node.args)
+ self.write('):')
+ self.body(node.body)
+
+ def visit_ClassDef(self, node):
+ have_args = []
+ def paren_or_comma():
+ if have_args:
+ self.write(', ')
+ else:
+ have_args.append(True)
+ self.write('(')
+
+ self.newline(extra=2)
+ self.decorators(node)
+ self.newline(node)
+ self.write('class %s' % node.name)
+ for base in node.bases:
+ paren_or_comma()
+ self.visit(base)
+ # XXX: the if here is used to keep this module compatible
+ # with python 2.6.
+ if hasattr(node, 'keywords'):
+ for keyword in node.keywords:
+ paren_or_comma()
+ self.write(keyword.arg + '=')
+ self.visit(keyword.value)
+ if node.starargs is not None:
+ paren_or_comma()
+ self.write('*')
+ self.visit(node.starargs)
+ if node.kwargs is not None:
+ paren_or_comma()
+ self.write('**')
+ self.visit(node.kwargs)
+ # TODO wtf???
+ self.write(have_args and '):' or ':')
+ self.body(node.body)
+
+ def visit_If(self, node):
+ self.newline(node)
+ self.write('if ')
+ self.visit(node.test)
+ self.write(':')
+ self.body(node.body)
+ while True:
+ else_ = node.orelse
+ if len(else_) == 1 and isinstance(else_[0], If):
+ node = else_[0]
+ self.newline()
+ self.write('elif ')
+ self.visit(node.test)
+ self.write(':')
+ self.body(node.body)
+ elif else_:
+ self.newline()
+ self.write('else:')
+ self.body(else_)
+ break
+ else:
+ break
+
+ def visit_For(self, node):
+ self.newline(node)
+ self.write('for ')
+ self.visit(node.target)
+ self.write(' in ')
+ self.visit(node.iter)
+ self.write(':')
+ self.body_or_else(node)
+
+ def visit_While(self, node):
+ self.newline(node)
+ self.write('while ')
+ self.visit(node.test)
+ self.write(':')
+ self.body_or_else(node)
+
+ def visit_With(self, node):
+ self.newline(node)
+ self.write('with ')
+ self.visit(node.context_expr)
+ if node.optional_vars is not None:
+ self.write(' as ')
+ self.visit(node.optional_vars)
+ self.write(':')
+ self.body(node.body)
+
+ def visit_Pass(self, node):
+ self.newline(node)
+ self.write('pass')
+
+ def visit_Print(self, node):
+ # XXX: python 2.6 only
+ self.newline(node)
+ self.write('print ')
+ want_comma = False
+ if node.dest is not None:
+ self.write(' >> ')
+ self.visit(node.dest)
+ want_comma = True
+ for value in node.values:
+ if want_comma:
+ self.write(', ')
+ self.visit(value)
+ want_comma = True
+ if not node.nl:
+ self.write(',')
+
+ def visit_Delete(self, node):
+ self.newline(node)
+ self.write('del ')
+ for idx, target in enumerate(node):
+ if idx:
+ self.write(', ')
+ self.visit(target)
+
+ def visit_TryExcept(self, node):
+ self.newline(node)
+ self.write('try:')
+ self.body(node.body)
+ for handler in node.handlers:
+ self.visit(handler)
+
+ def visit_TryFinally(self, node):
+ self.newline(node)
+ self.write('try:')
+ self.body(node.body)
+ self.newline(node)
+ self.write('finally:')
+ self.body(node.finalbody)
+
+ def visit_Global(self, node):
+ self.newline(node)
+ self.write('global ' + ', '.join(node.names))
+
+ def visit_Nonlocal(self, node):
+ self.newline(node)
+ self.write('nonlocal ' + ', '.join(node.names))
+
+ def visit_Return(self, node):
+ self.newline(node)
+ self.write('return ')
+ self.visit(node.value)
+
+ def visit_Break(self, node):
+ self.newline(node)
+ self.write('break')
+
+ def visit_Continue(self, node):
+ self.newline(node)
+ self.write('continue')
+
+ def visit_Raise(self, node):
+ # XXX: Python 2.6 / 3.0 compatibility
+ self.newline(node)
+ self.write('raise')
+ if hasattr(node, 'exc') and node.exc is not None:
+ self.write(' ')
+ self.visit(node.exc)
+ if node.cause is not None:
+ self.write(' from ')
+ self.visit(node.cause)
+ elif hasattr(node, 'type') and node.type is not None:
+ self.visit(node.type)
+ if node.inst is not None:
+ self.write(', ')
+ self.visit(node.inst)
+ if node.tback is not None:
+ self.write(', ')
+ self.visit(node.tback)
+
+ # Expressions
+
+ def visit_Attribute(self, node):
+ self.visit(node.value)
+ self.write('.' + node.attr)
+
+ def visit_Call(self, node):
+ want_comma = []
+ def write_comma():
+ if want_comma:
+ self.write(', ')
+ else:
+ want_comma.append(True)
+
+ self.visit(node.func)
+ self.write('(')
+ for arg in node.args:
+ write_comma()
+ self.visit(arg)
+ for keyword in node.keywords:
+ write_comma()
+ self.write(keyword.arg + '=')
+ self.visit(keyword.value)
+ if node.starargs is not None:
+ write_comma()
+ self.write('*')
+ self.visit(node.starargs)
+ if node.kwargs is not None:
+ write_comma()
+ self.write('**')
+ self.visit(node.kwargs)
+ self.write(')')
+
+ def visit_Name(self, node):
+ self.write(node.id)
+
+ def visit_Str(self, node):
+ self.write(repr(node.s))
+
+ def visit_Bytes(self, node):
+ self.write(repr(node.s))
+
+ def visit_Num(self, node):
+ self.write(repr(node.n))
+
+ def visit_Tuple(self, node):
+ self.write('(')
+ idx = -1
+ for idx, item in enumerate(node.elts):
+ if idx:
+ self.write(', ')
+ self.visit(item)
+ # TODO wtf???
+ self.write(idx and ')' or ',)')
+
+ def sequence_visit(left, right):
+ def visit(self, node):
+ self.write(left)
+ for idx, item in enumerate(node.elts):
+ if idx:
+ self.write(', ')
+ self.visit(item)
+ self.write(right)
+ return visit
+
+ visit_List = sequence_visit('[', ']')
+ visit_Set = sequence_visit('{', '}')
+ del sequence_visit
+
+ def visit_Dict(self, node):
+ self.write('{')
+ for idx, (key, value) in enumerate(zip(node.keys, node.values)):
+ if idx:
+ self.write(', ')
+ self.visit(key)
+ self.write(': ')
+ self.visit(value)
+ self.write('}')
+
+ def visit_BinOp(self, node):
+ self.visit(node.left)
+ self.write(' %s ' % self.BINOP_SYMBOLS[node.op])
+ self.visit(node.right)
+
+ def visit_BoolOp(self, node):
+ self.write('(')
+ for idx, value in enumerate(node.values):
+ if idx:
+ self.write(' %s ' % self.BOOLOP_SYMBOLS[node.op])
+ self.visit(value)
+ self.write(')')
+
+ def visit_Compare(self, node):
+ self.write('(')
+ self.visit(node.left)
+ for op, right in zip(node.ops, node.comparators):
+ self.write(' %s ' % self.CMPOP_SYMBOLS[op])
+ self.visit(right)
+ self.write(')')
+
+ def visit_UnaryOp(self, node):
+ self.write('(')
+ op = self.UNARYOP_SYMBOLS[node.op]
+ self.write(op)
+ if op == 'not':
+ self.write(' ')
+ self.visit(node.operand)
+ self.write(')')
+
+ def visit_Subscript(self, node):
+ self.visit(node.value)
+ self.write('[')
+ self.visit(node.slice)
+ self.write(']')
+
+ def visit_Slice(self, node):
+ if node.lower is not None:
+ self.visit(node.lower)
+ self.write(':')
+ if node.upper is not None:
+ self.visit(node.upper)
+ if node.step is not None:
+ self.write(':')
+ if not (isinstance(node.step, Name) and node.step.id == 'None'):
+ self.visit(node.step)
+
+ def visit_ExtSlice(self, node):
+ for idx, item in node.dims:
+ if idx:
+ self.write(', ')
+ self.visit(item)
+
+ def visit_Yield(self, node):
+ self.write('yield ')
+ self.visit(node.value)
+
+ def visit_Lambda(self, node):
+ self.write('lambda ')
+ self.signature(node.args)
+ self.write(': ')
+ self.visit(node.body)
+
+ def visit_Ellipsis(self, node):
+ self.write('Ellipsis')
+
+ def generator_visit(left, right):
+ def visit(self, node):
+ self.write(left)
+ self.visit(node.elt)
+ for comprehension in node.generators:
+ self.visit(comprehension)
+ self.write(right)
+ return visit
+
+ visit_ListComp = generator_visit('[', ']')
+ visit_GeneratorExp = generator_visit('(', ')')
+ visit_SetComp = generator_visit('{', '}')
+ del generator_visit
+
+ def visit_DictComp(self, node):
+ self.write('{')
+ self.visit(node.key)
+ self.write(': ')
+ self.visit(node.value)
+ for comprehension in node.generators:
+ self.visit(comprehension)
+ self.write('}')
+
+ def visit_IfExp(self, node):
+ self.visit(node.body)
+ self.write(' if ')
+ self.visit(node.test)
+ self.write(' else ')
+ self.visit(node.orelse)
+
+ def visit_Starred(self, node):
+ self.write('*')
+ self.visit(node.value)
+
+ def visit_Repr(self, node):
+ # XXX: python 2.6 only
+ self.write('`')
+ self.visit(node.value)
+ self.write('`')
+
+ # Helper Nodes
+
+ def visit_alias(self, node):
+ self.write(node.name)
+ if node.asname is not None:
+ self.write(' as ' + node.asname)
+
+ def visit_comprehension(self, node):
+ self.write(' for ')
+ self.visit(node.target)
+ self.write(' in ')
+ self.visit(node.iter)
+ if node.ifs:
+ for if_ in node.ifs:
+ self.write(' if ')
+ self.visit(if_)
+
+ def visit_excepthandler(self, node):
+ self.newline(node)
+ self.write('except')
+ if node.type is not None:
+ self.write(' ')
+ self.visit(node.type)
+ if node.name is not None:
+ self.write(' as ')
+ self.visit(node.name)
+ self.write(':')
+ self.body(node.body)