From 2ce8a7b3da346ad20dda2fe13522d7dfba1286ce Mon Sep 17 00:00:00 2001 From: Marion Zepf Date: Sun, 27 Oct 2013 02:08:57 +0000 Subject: python export --- 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) -- cgit v0.9.1