diff options
-rw-r--r-- | TurtleArt/tabasics.py | 31 | ||||
-rw-r--r-- | TurtleArt/taconstants.py | 3 | ||||
-rw-r--r-- | TurtleArt/taprimitive.py | 275 | ||||
-rw-r--r-- | TurtleArt/tatype.py | 269 |
4 files changed, 470 insertions, 108 deletions
diff --git a/TurtleArt/tabasics.py b/TurtleArt/tabasics.py index e326ecd..d57a8ff 100644 --- a/TurtleArt/tabasics.py +++ b/TurtleArt/tabasics.py @@ -72,7 +72,8 @@ from tapalette import (make_palette, define_logo_function) from talogo import (primitive_dictionary, logoerror) from tautils import (convert, chr_to_ord, round_int, strtype) from taconstants import (Color, CONSTANTS) -from taprimitive import Primitive +from taprimitive import (ArgSlot, ConstantArg, Primitive) +from tatype import * # TODO reduce to necessary objects from taturtle import Turtle @@ -95,9 +96,10 @@ class Palettes(): self.tw = turtle_window self.prim_cache = { - "check_number": Primitive(self.check_number, export_me=False), - "convert_value_for_move": Primitive(self.convert_value_for_move, - export_me=False), + "check_number": Primitive(self.check_number, + arg_descs=[ArgSlot(TYPE_OBJECT)], export_me=False), + "convert_value_for_move": Primitive(self.convert_value_for_move, + arg_descs=[ArgSlot(TYPE_OBJECT)], export_me=False), "convert_for_cmp": Primitive(Primitive.convert_for_cmp, constant_args={'decimal_point': self.tw.decimal_point}), "convert_to_number": Primitive(Primitive.convert_to_number, @@ -139,7 +141,8 @@ class Palettes(): 'forward', 1, Primitive(Turtle.forward, - slot_wrappers={0: self.prim_cache["convert_value_for_move"]}, + arg_descs=[ArgSlot(TYPE_NUMBER, + wrapper=self.prim_cache["convert_value_for_move"])], call_afterwards=self.after_move)) palette.add_block('back', @@ -200,7 +203,8 @@ degrees)')) 'right', 1, Primitive(Turtle.right, - slot_wrappers={0: self.prim_cache["check_number"]}, + arg_descs=[ArgSlot(TYPE_NUMBER, + wrapper=self.prim_cache["check_number"])], call_afterwards=self.after_right)) palette.add_block('arc', @@ -871,10 +875,11 @@ number of seconds')) help_string=_('loops specified number of times')) self.tw.lc.def_prim('repeat', 2, Primitive(self.tw.lc.prim_loop, - slot_wrappers={0: Primitive(Primitive.controller_repeat, - slot_wrappers={0: Primitive(self.tw.lc.int, - slot_wrappers={0: self.prim_cache["check_number"] - })})}), + arg_descs=[ArgSlot(TYPE_OBJECT, wrapper=Primitive(Primitive.controller_repeat, + arg_descs=[ArgSlot(TYPE_NUMBER, wrapper=Primitive(self.tw.lc.int, return_type=TYPE_NUMBER, + arg_descs=[ArgSlot(TYPE_OBJECT, wrapper=self.prim_cache["check_number"] + )]))])), + ArgSlot(TYPE_OBJECT)]), True) palette.add_block('if', @@ -953,11 +958,11 @@ boolean operators from Numbers palette')) help_string=_('connects action to toolbar run \ buttons')) self.tw.lc.def_prim('start', 0, - Primitive(Primitive.group, constant_args={0: [ - Primitive(self.tw.lc.prim_start, call_me=False, + Primitive(Primitive.group, arg_descs=[ConstantArg([ + Primitive(self.tw.lc.prim_start, export_me=False), Primitive(self.tw.lc.prim_define_stack, - constant_args={0: 'start'}, call_me=False)]})) + arg_descs=[ConstantArg('start')])])])) palette.add_block('string', style='box-style', diff --git a/TurtleArt/taconstants.py b/TurtleArt/taconstants.py index ba0b5a7..cd1ece2 100644 --- a/TurtleArt/taconstants.py +++ b/TurtleArt/taconstants.py @@ -103,6 +103,9 @@ class Color(object): def __float__(self): return float(int(self)) + def get_number_string(self): + return str(int(self)) + def __str__(self): return str(self.name) diff --git a/TurtleArt/taprimitive.py b/TurtleArt/taprimitive.py index bac9f7b..669abf0 100644 --- a/TurtleArt/taprimitive.py +++ b/TurtleArt/taprimitive.py @@ -20,13 +20,15 @@ import ast from gettext import gettext as _ +import traceback #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 talogo import (LogoCode, logoerror) from taturtle import (Turtle, Turtles) +from tatype import * # TODO import only the objects that we really need here from tautils import debug_output from tawindow import (global_objects, TurtleArtWindow) @@ -69,65 +71,58 @@ class Primitive(object): 'less': ast.Lt, 'greater': ast.Gt} - def __init__(self, func, constant_args=None, slot_wrappers=None, - call_afterwards=None, call_me=True, export_me=True): - """ constant_args -- A dictionary containing constant arguments to be - passed to the function. It uses the same key scheme as - slot_wrappers, except that argument ranges are not supported. - The constant args and kwargs are added to the runtime args and - kwargs before the slot wrappers are called. - slot_wrappers -- A dictionary mapping from the index of an - argument in the args list to another Primitive that should be - wrapped around the actual argument value (e.g., to convert a - positive number to a negative one). For keyword arguments, the - key in slot_wrappers should be the same as the kwargs key. To pass - multiple arguments to the slot wrapper, use a tuple of the first - and last argument number (the latter increased by 1) as a key. - Negative argument indices are not supported. + def __init__(self, func, return_type=TYPE_OBJECT, arg_descs=None, kwarg_descs=None, + call_afterwards=None, export_me=True, + # deprecated: + call_me=True, slot_wrappers=None, constant_args=None): + """ return_type -- the type (from the type hierarchy) that this + Primitive will return + # TODO make mandatory! + arg_descs, kwarg_descs -- a list of argument descriptions and + a dictionary of keyword argument descriptions. An argument + description can be either an ArgSlot or a ConstantArg. call_afterwards -- Code to call after this Primitive has been called (e.g., for updating labels in LogoCode) (not used for creating AST) call_me -- True if this Primitive should be called (default), False if it should be passed on as a Primitive object + # TODO obsolete ??? export_me -- True iff this Primitive should be exported to Python code (the default case) """ self.func = func + self.return_type = return_type - if constant_args is None: - self.constant_args = {} + if arg_descs is None: + self.arg_descs = [] else: - self.constant_args = constant_args + self.arg_descs = arg_descs - if slot_wrappers is None: - self.slot_wrappers = {} + if kwarg_descs is None: + self.kwarg_descs = {} else: - # check for duplicate argument indices - msg = ("argument at index %d is associated with multiple slot " - "wrappers") - nums = set() - tuples = [] - for k in slot_wrappers.keys(): - if isinstance(k, int): - nums.add(k) - elif isinstance(k, tuple): - tuples.append(k) - tuples.sort() - prev_tuple = (0, 0) - for tuple_ in tuples: - if prev_tuple[1] > tuple_[0]: - raise KeyError(msg % (tuple_[0])) - for i in range(*tuple_): - if i in nums: - raise KeyError(msg % (i)) - prev_tuple = tuple_ - self.slot_wrappers = slot_wrappers + self.kwarg_descs = kwarg_descs self.call_afterwards = call_afterwards self.call_me = call_me self.export_me = export_me + def copy(self): + """ Return a Primitive object with the same attributes as this one. + Shallow-copy the arg_descs and kwarg_descs attributes. """ + return Primitive(self.func, + return_type=self.return_type, + arg_descs=self.arg_descs[:], + kwarg_descs=self.kwarg_descs.copy(), + call_afterwards=self.call_afterwards, + call_me=self.call_me, + export_me=self.export_me) + def __repr__(self): - return "Primitive(" + repr(self.func) + ")" + return "Primitive(%s)" % (repr(self.func)) + + @property + def __name__(self): + return self.func.__name__ def _apply_wrappers(self, runtime_args, runtime_kwargs, convert_to_ast=False): @@ -255,56 +250,84 @@ class Primitive(object): return (all_args, all_kwargs) + def are_slots_filled(self): + """ Return True iff none of the arg_descs or kwarg_descs is an + ArgSlot. """ + for arg_desc in self.arg_descs: + if isinstance(arg_desc, ArgSlot): + return False + for key in self.kwarg_descs: + if isinstance(self.kwarg_descs[key], ArgSlot): + return False + return True + + def fill_slots(self, *arguments, **keywords): + """ Return a copy of this Primitive whose ArgSlots are filled with + the given arguments, turned into ConstantArgs. Call the arguments, + apply their wrappers, and check their types as appropriate. """ + new_prim = self.copy() + fillers = list(arguments[:]) + i = 0 + while i < len(new_prim.arg_descs): + arg_desc = new_prim.arg_descs[i] + if isinstance(arg_desc, ArgSlot): + f = fillers.pop(0) + value = arg_desc.fill(f) + new_prim.arg_descs[i] = ConstantArg(value) + i += 1 + for key in keywords: + kwarg_desc = new_prim.kwarg_descs[key] + if isinstance(kwarg_desc, ArgSlot): + value = kwarg_desc.fill(keywords[key]) + new_prim.kwarg_descs[key] = value + return new_prim + 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. """ + If the very first argument is a LogoCode instance, it is removed. + The active turtle, the Turtles object, the canvas, the LogoCode + object, or the TurtleArtWindow object will be prepended to the + arguments (depending on what this Primitive wants). """ # remove the first argument if it is a LogoCode instance if runtime_args and isinstance(runtime_args[0], LogoCode): runtime_args = runtime_args[1:] - runtime_args_copy = runtime_args[:] - runtime_args = [] - for arg in runtime_args_copy: - if isinstance(arg, tuple) and arg and callable(arg[0]): - runtime_args.append(arg[0](*arg[1:])) - else: - runtime_args.append(arg) + # fill the ArgSlots with the runtime arguments + new_prim = self.fill_slots(*runtime_args, **runtime_kwargs) + if not new_prim.are_slots_filled(): + raise logoerror('not enough arguments') # TODO find correct error message + + # extract the actual values from the (now constant) arguments + new_args = [c_arg.value for c_arg in new_prim.arg_descs] + new_kwargs = {} + for key in new_prim.kwarg_descs: + new_kwargs[key] = new_prim.kwarg_descs[key].value # what does this primitive want as its first argument? - if self.wants_turtle(): + if new_prim.wants_turtle(): first_arg = global_objects["turtles"].get_active_turtle() - elif self.wants_turtles(): + elif new_prim.wants_turtles(): first_arg = global_objects["turtles"] - elif self.wants_canvas(): + elif new_prim.wants_canvas(): first_arg = global_objects["canvas"] - elif self.wants_logocode(): + elif new_prim.wants_logocode(): first_arg = global_objects["logo"] - elif self.wants_tawindow(): + elif new_prim.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) + if first_arg is None or is_bound_instancemethod(new_prim.func): + return_value = new_prim.func(*new_args, **new_kwargs) else: - return_value = self.func(first_arg, *new_args, **new_kwargs) + return_value = new_prim.func(first_arg, *new_args, **new_kwargs) - if self.call_afterwards is not None: - self.call_afterwards(*new_args, **new_kwargs) + if new_prim.call_afterwards is not None: + new_prim.call_afterwards(*new_args, **new_kwargs) return return_value @@ -879,20 +902,97 @@ class Primitive(object): return arg1 > arg2 +class ArgSlot(object): + """ Description of the requirements that a Primitive demands from an + argument or keyword argument. An ArgSlot is filled at runtime, based + on the block program structure. """ + + def __init__(self, type_, call_arg=True, wrapper=None): + """ + type_ -- what type of the type hierarchy the argument should have + (after the wrapper has been applied) + call_arg -- if this argument is callable, should it be called and + its return value passed to the parent Primitive (True, the + default), or should it be passed as it is (False)? + wrapper -- a Primitive that is 'wrapped around' the argument before + it gets passed to its parent Primitive. Wrappers can be nested + infinitely. """ + self.type = type_ + self.call_arg = call_arg + self.wrapper = wrapper + + def __repr__(self): + return "ArgSlot(type=%s, call=%s, wrapper=%s)" % (repr(self.type), + repr(self.call_arg), repr(self.wrapper)) + + def fill(self, argument, convert_to_ast=False): + """ Fill this argument slot with the given argument """ + (arg_type, is_an_ast) = get_type(argument) + if is_an_ast: + convert_to_ast = True + elif convert_to_ast: + argument = value_to_ast(argument) + + # 1. Call the argument? + # TODO not just when it's a tuple ??? + if isinstance(argument, tuple) and argument and callable(argument[0]): + func = argument[0] + args = argument[1:] + if convert_to_ast: + call_ast = get_call_ast(func.__name__, + [value_to_ast(arg) for arg in args]) + if self.call_arg: + if convert_to_ast: + argument = call_ast + else: + argument = func(*args) + else: + if convert_to_ast: + argument = ast.Lambda(args=[], vararg=None, kwarg=None, + defaults=[], body=call_ast) + else: + if isinstance(func, Primitive): + argument = func.fill_slots(*args) + else: + argument = Primitive(func, + [ConstantArg(arg) for arg in args]) + + # 2. apply any wrappers + if convert_to_ast: + if hasattr(self.wrapper, "get_ast"): + argument = self.wrapper.get_ast(argument) + else: + raise PyExportError("cannot convert callable %s to an AST" + % (repr(self.wrapper))) + elif callable(self.wrapper): + argument = self.wrapper(argument) + + # 3. check the type and convert the argument if necessary + try: + argument = convert(argument, self.type) + except ConversionError: + traceback.print_exc() + # TODO find appropriate logoerror message depending on type combination + raise logoerror("type problem") + + return argument + -def is_instancemethod(method): - # TODO how to access the type `instancemethod` directly? - return type(method).__name__ == "instancemethod" +class ConstantArg(object): + """ A constant argument or keyword argument to a Primitive. It is + independent of the block program structure. """ -def is_bound_instancemethod(method): - return is_instancemethod(method) and method.im_self is not None + def __init__(self, value): + self.value = value + + def __repr__(self): + return "ConstantArg(%s)" % (repr(self.value)) -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" +class Or(tuple): + """ Disjunction of two or more arguments or argument types """ + pass + def value_to_ast(value, *args_for_prim, **kwargs_for_prim): @@ -954,21 +1054,6 @@ def ast_to_value(ast_object): 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 diff --git a/TurtleArt/tatype.py b/TurtleArt/tatype.py new file mode 100644 index 0000000..c7c1fe3 --- /dev/null +++ b/TurtleArt/tatype.py @@ -0,0 +1,269 @@ +#Copyright (c) 2013 Marion Zepf + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + +""" type system for Primitives and their arguments """ + +import ast +from gettext import gettext as _ + +from taconstants import (Color, CONSTANTS) +from tautils import debug_output + + +# types are defined as numbers because they are faster to compare than strings +TYPE_OBJECT = 0 +TYPE_CHAR = 1 +TYPE_COLOR = 2 +TYPE_FLOAT = 3 +TYPE_INT = 4 +TYPE_NEGATIVE = 5 +TYPE_NUMBER = 6 +TYPE_NUMERIC_STRING = 7 +TYPE_POSITIVE = 8 +TYPE_STRING = 9 +TYPE_ZERO = 10 +ARG_TYPES = ( + # possible types of arguments to Primitives + TYPE_CHAR, TYPE_COLOR, TYPE_FLOAT, TYPE_INT, TYPE_NEGATIVE, TYPE_NUMBER, + TYPE_NUMERIC_STRING, TYPE_OBJECT, TYPE_POSITIVE, TYPE_STRING, TYPE_ZERO + # TODO add list types +) + + +def get_type(x): + """ Return the most specific type in the type hierarchy that applies to x + and a boolean indicating whether x is an AST. If the type cannot be + determined, return TYPE_OBJECT as the type. """ + # non-AST types + if isinstance(x, (float, int, long)): + if x > 0: + return (TYPE_POSITIVE, False) + elif x == 0: + return (TYPE_ZERO, False) + else: + return (TYPE_NEGATIVE, False) + elif isinstance(x, basestring): + if len(x) == 1: + return (TYPE_CHAR, False) + try: + float(x) + except ValueError: + return (TYPE_STRING, False) + else: + return (TYPE_NUMERIC_STRING, False) + elif isinstance(x, Color): + return (TYPE_COLOR, False) + elif hasattr(x, "return_type"): + return (x.return_type, False) + + # AST types + elif isinstance(x, ast.Num): + return (get_type(x.n)[0], True) + elif isinstance(x, ast.Str): + return (get_type(x.s)[0], True) + elif isinstance(x, ast.Name): + try: + # we need to have imported CONSTANTS for this to work + value = eval(x.id) + except NameError: + return (TYPE_OBJECT, True) + else: + return (get_type(value)[0], True) + elif isinstance(x, ast.Call): + if x.func.__name__ in (TYPE_FLOAT, TYPE_INT): + return (x.func.__name__, True) + elif x.func.__name__ in ('str', 'unicode'): + return (TYPE_STRING, True) + + return (TYPE_OBJECT, isinstance(x, ast.AST)) + + +def is_instancemethod(method): + # TODO how to access the type `instancemethod` directly? + return type(method).__name__ == "instancemethod" + +def is_bound_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 identity(x): + return x + +TYPE_CONVERTERS = { + # Type hierarchy: If there is a converter A -> B, then A is a subtype of B. + # The converter from A to B is stored under TYPE_CONVERTERS[A][B]. + # The relation describing the type hierarchy must be transitive, i.e. + # converting A -> C must yield the same result as converting A -> B -> C. + # TYPE_OBJECT is the supertype of everything. + TYPE_CHAR: { + TYPE_POSITIVE: ord, # ignore the zero byte, chr(0) + TYPE_STRING: identity}, + TYPE_COLOR: { + TYPE_FLOAT: float, + TYPE_INT: int, + TYPE_NUMBER: int, + TYPE_STRING: Color.get_number_string}, + TYPE_FLOAT: { + TYPE_INT: int, + TYPE_NUMBER: identity, + TYPE_STRING: str}, + TYPE_INT: { + TYPE_FLOAT: float, + TYPE_NUMBER: identity, + TYPE_STRING: str}, + TYPE_NEGATIVE: { + TYPE_FLOAT: float, + TYPE_INT: int, + TYPE_NUMBER: identity, + TYPE_STRING: str}, + TYPE_NUMBER: { + TYPE_STRING: str}, + TYPE_NUMERIC_STRING: { + TYPE_FLOAT: float, + TYPE_STRING: identity}, + TYPE_POSITIVE: { + TYPE_FLOAT: float, + TYPE_INT: int, + TYPE_NUMBER: identity, + TYPE_STRING: str}, + TYPE_ZERO: { + TYPE_FLOAT: float, + TYPE_INT: int, + TYPE_NUMBER: identity, + TYPE_STRING: str} +} + + +def get_call_ast(func_name, args=None, keywords=None): + """ Return an AST representing the call to a function with the name + func_name, passing it the arguments args (given as a list) and the + keyword arguments keywords (given as a dictionary). """ + if args is None: + args = [] + if keywords is None: + keywords = {} + return ast.Call(func=ast.Name(id=func_name, + ctx=ast.Load), + args=args, + keywords=keywords, + starargs=None, + kwargs=None) + + +class ConversionError(BaseException): + """ Error that is raised when something goes wrong during type conversion """ + + def __init__(self, message): + """ message -- the error message """ + self.message = message + + def __str__(self): + return _("error during type conversion") + ": " + str(self.message) + + +def convert(x, new_type, old_type=None): + """ Convert x to the new type if possible. + old_type -- the type of x. If not given, it is computed. """ + if new_type not in ARG_TYPES: + raise ValueError('%s is not a type in the type hierarchy' + % (repr(new_type))) + # every type can be converted to TYPE_OBJECT + if new_type == TYPE_OBJECT: + return x + if old_type not in ARG_TYPES: + (old_type, is_an_ast) = get_type(x) + else: + is_an_ast = isinstance(x, ast.AST) + # every type can be converted to itself + if old_type == new_type: + return x + + # is there a converter for this pair of types? + converters_from_old = TYPE_CONVERTERS.get(old_type) + if converters_from_old is not None: + converter = converters_from_old.get(new_type) + if converter is not None: + # apply the converter + if is_an_ast: + if converter == identity: + return x + elif is_instancemethod(converter): + func = ast.Attribute(value=x, + attr=converter.im_func.__name__, + ctx=ast.Load) + return ast.Call(func=func, + args=[], + keywords={}, + starargs=None, + kwargs=None) + else: + func_name = converter.__name__ + return get_call_ast(func_name, x) + else: + return converter(x) + else: + # form the transitive closure of all types that old_type can be + # converted to, and look for new_type there + backtrace = converters_from_old.copy() + new_backtrace = backtrace + break_all = False + while True: + newest_backtrace = {} + for t in new_backtrace: + for new_t in TYPE_CONVERTERS.get(t, {}): + if new_t not in backtrace: + newest_backtrace[new_t] = t + backtrace[new_t] = t + if new_t == new_type: + break_all = True + break + if break_all: + break + if break_all or not newest_backtrace: + break + new_backtrace = newest_backtrace + # use the backtrace to find the path from old_type to new_type + if new_type in backtrace: + conversion_path = [new_type] + while conversion_path[-1] in backtrace: + conversion_path.append(backtrace[conversion_path[-1]]) + # the last thing must be the conversion function + conversion_path.pop() + # the rest are the intermediate types and new_type + result = x + intermed_type = old_type + while conversion_path: + t = conversion_path.pop() + result = convert(result, t, old_type=intermed_type) + intermed_type = t + return result + + # if we have not returned the result, then there is no converter available + raise ConversionError("type %s cannot be converted to type %s" + % (repr(old_type), repr(new_type))) + + |