Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/TurtleArt/tatype.py
diff options
context:
space:
mode:
authorMarion <marion.zepf@gmail.com>2013-08-23 09:50:55 (GMT)
committer Marion <marion.zepf@gmail.com>2013-08-23 09:50:55 (GMT)
commit6e41c1cdd8e4348ed9bdedbeaef8728364af72f2 (patch)
treed6ccb81f961b8429a340653d79227980c6e84d8a /TurtleArt/tatype.py
parent54969017ae7836c371a61a862e8d35761076b06f (diff)
introduce type system for Primitives and simplify argument slot definitions
- This commit breaks everything! Only the square example still works. The rest will be fixed in subsequent commits.
Diffstat (limited to 'TurtleArt/tatype.py')
-rw-r--r--TurtleArt/tatype.py269
1 files changed, 269 insertions, 0 deletions
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)))
+
+