Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TurtleArt/tabasics.py31
-rw-r--r--TurtleArt/taconstants.py3
-rw-r--r--TurtleArt/taprimitive.py275
-rw-r--r--TurtleArt/tatype.py269
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)))
+
+