Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/TurtleArt/taprimitive.py
diff options
context:
space:
mode:
authorMarion <marion.zepf@gmail.com>2013-09-04 15:08:07 (GMT)
committer Marion <marion.zepf@gmail.com>2013-09-04 15:08:07 (GMT)
commit97b945e1e088c2e51d9a6233be49c401040883e9 (patch)
treeb9ae9cb56638f9bf7291b5b762f7ce942e7cc32b /TurtleArt/taprimitive.py
parentcc420f0e37c82275bb5d6b46d85aef9cb9fdae91 (diff)
parente0fcb7656caf8d35e3e4bfa9ce99729328f35b1d (diff)
Merge branch 'type-system' into type-system-while-until
Conflicts: TurtleArt/taprimitive.py -- preserve the semantics of both changes
Diffstat (limited to 'TurtleArt/taprimitive.py')
-rw-r--r--TurtleArt/taprimitive.py239
1 files changed, 64 insertions, 175 deletions
diff --git a/TurtleArt/taprimitive.py b/TurtleArt/taprimitive.py
index 6e23ad0..ca0e73a 100644
--- a/TurtleArt/taprimitive.py
+++ b/TurtleArt/taprimitive.py
@@ -28,10 +28,10 @@ from tacanvas import TurtleGraphics
from taconstants import (Color, CONSTANTS)
from talogo import (LogoCode, logoerror)
from taturtle import (Turtle, Turtles)
-from tatype import (convert, get_call_ast, get_converter, get_type,
- is_bound_instancemethod, is_instancemethod,
+from tatype import (ACTION_AST, BOX_AST, convert, get_call_ast, get_converter,
+ get_type, is_bound_instancemethod, is_instancemethod,
is_staticmethod, TATypeError, Type, TypeDisjunction,
- TYPE_FLOAT, TYPE_OBJECT)
+ TYPE_COLOR, TYPE_FLOAT, TYPE_OBJECT)
from tautils import debug_output
from tawindow import (global_objects, TurtleArtWindow)
from util import ast_extensions
@@ -187,12 +187,11 @@ class Primitive(object):
if isinstance(slot, ArgSlot):
filler = filler_list.pop(0)
try:
- value = slot.fill(filler,convert_to_ast=convert_to_ast)
+ const = slot.fill(filler,convert_to_ast=convert_to_ast)
except TATypeError as error:
break
else:
- new_slot_list.append(ConstantArg(value,
- call_arg=slot.call_arg))
+ new_slot_list.append(const)
else:
new_slot_list.append(slot)
if error is None:
@@ -205,10 +204,9 @@ class Primitive(object):
for key in keywords:
kwarg_desc = new_prim.kwarg_descs[key]
if isinstance(kwarg_desc, ArgSlot):
- value = kwarg_desc.fill(keywords[key],
+ const = kwarg_desc.fill(keywords[key],
convert_to_ast=convert_to_ast)
- # TODO don't we need the ConstantArg constructor here as well?
- new_prim.kwarg_descs[key] = value
+ new_prim.kwarg_descs[key] = const
return new_prim
@@ -297,7 +295,7 @@ class Primitive(object):
debug_output(" arg_asts: " + repr(arg_asts))
new_prim = self.fill_slots(arg_asts, kwarg_asts, convert_to_ast=True)
if not new_prim.are_slots_filled():
- raise PyExportError("not enough arguments")
+ raise PyExportError("not enough arguments") # TODO better msg
if Primitive._DEBUG:
debug_output(" new_prim.arg_descs: " + repr(new_prim.arg_descs))
@@ -354,23 +352,21 @@ class Primitive(object):
# 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
+ target_ast = ast.Subscript(value=BOX_AST,
+ slice=ast.Index(value=new_arg_asts[0]), ctx=ast.Store)
+ return ast.Assign(targets=[target_ast], value=new_arg_asts[1])
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)
+ return ast.Subscript(value=BOX_AST,
+ slice=ast.Index(value=new_arg_asts[0]), 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])
+ stack_func = ast.Subscript(value=ACTION_AST,
+ slice=ast.Index(value=new_arg_asts[0]), ctx=ast.Load)
+ call_ast = get_call_ast('logo.icall', [stack_func])
+ return [call_ast, ast_yield_true()]
# standard operators
elif self.func.__name__ in Primitive.STANDARD_OPERATORS:
@@ -381,7 +377,8 @@ class Primitive(object):
return get_type(x)[0] == TYPE_FLOAT
if ( not _is_float(new_arg_asts[0]) and
not _is_float(new_arg_asts[1])):
- new_arg_asts[0] = get_call_ast('float', [new_arg_asts[0]])
+ new_arg_asts[0] = get_call_ast('float', [new_arg_asts[0]],
+ return_type=TYPE_FLOAT)
if len(new_arg_asts) == 1:
if isinstance(op, tuple):
op = op[0]
@@ -406,11 +403,8 @@ class Primitive(object):
# square root
elif self == Primitive.square_root:
- return get_call_ast('sqrt', new_arg_asts, new_kwarg_asts)
-
- # type conversion # TODO remove when obsolete
- elif self in (Primitive.convert_for_cmp, Primitive.convert_to_number):
- return self.func(*new_arg_asts, **new_kwarg_asts)
+ return get_call_ast('sqrt', new_arg_asts, new_kwarg_asts,
+ return_type=self.return_type)
# identity
elif self == Primitive.identity:
@@ -440,7 +434,8 @@ class Primitive(object):
else:
func_name = self.get_name_for_export()
- return get_call_ast(func_name, new_arg_asts, new_kwarg_asts)
+ return get_call_ast(func_name, new_arg_asts, new_kwarg_asts,
+ return_type=self.return_type)
def __eq__(self, other):
""" Two Primitives are equal iff their all their properties are equal.
@@ -594,77 +589,6 @@ class Primitive(object):
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. """
@@ -725,59 +649,6 @@ class Primitive(object):
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
@@ -820,11 +691,6 @@ class Disjunction(tuple):
return self
-# make TypeDisjunction 'inherit' the methods of the abstract Disjunction class
-TypeDisjunction.__repr__ = Disjunction.__repr__
-TypeDisjunction.get_alternatives = Disjunction.get_alternatives
-
-
class PrimitiveDisjunction(Disjunction,Primitive):
""" Disjunction of two or more Primitives. PrimitiveDisjunctions may not
be nested. """
@@ -899,8 +765,9 @@ class ArgSlot(object):
return (self, )
def fill(self, argument, convert_to_ast=False):
- """ Try to fill this argument slot with the given argument. If there
- is a type problem, raise a TATypeError. """
+ """ Try to fill this argument slot with the given argument. Return
+ a ConstantArg containing the result. If there is a type problem,
+ raise a TATypeError. """
if isinstance(argument, ast.AST):
convert_to_ast = True
@@ -935,23 +802,26 @@ class ArgSlot(object):
# check if the argument can fill this slot (type-wise)
if wrapper is not None:
- arg_type = get_type(wrapper)[0]
+ arg_types = get_type(wrapper)[0]
bad_value = wrapper
elif func is not None:
- arg_type = get_type(func)[0]
+ arg_types = get_type(func)[0]
bad_value = func
else:
- arg_type = get_type(argument)[0]
+ arg_types = get_type(argument)[0]
bad_value = argument
converter = None
+ if not isinstance(arg_types, TypeDisjunction):
+ arg_types = TypeDisjunction((arg_types, ))
if isinstance(slot.type, TypeDisjunction):
- for type_ in slot.type:
- converter = get_converter(arg_type, type_)
+ slot_types = slot.type
+ else:
+ slot_types = TypeDisjunction((slot.type, ))
+ for old_type in arg_types:
+ for new_type in slot_types:
+ converter = get_converter(old_type, new_type)
if converter is not None:
break
- else:
- type_ = slot.type
- converter = get_converter(arg_type, type_)
# unable to convert, try next wrapper/ slot/ func
if converter is None:
continue
@@ -1010,22 +880,23 @@ class ArgSlot(object):
# 3. check the type and convert the argument if necessary
try:
- converted_argument = convert(wrapped_argument, type_,
- converter=converter)
+ converted_argument = convert(wrapped_argument,
+ new_type, old_type=old_type, converter=converter)
except TATypeError as error:
# on failure, try next wrapper/ slot/ func
bad_value = wrapped_argument
continue
else:
# on success, return the result
- return converted_argument
+ return ConstantArg(converted_argument,
+ value_type=new_type, call_arg=slot.call_arg)
# if we haven't returned anything yet, then all alternatives failed
if error is not None:
raise error
else:
- raise TATypeError(bad_value=bad_value, bad_type=arg_type,
- req_type=type_,
+ raise TATypeError(bad_value=bad_value, bad_type=old_type,
+ req_type=new_type,
message="filling slot " + repr(self))
@@ -1038,9 +909,13 @@ class ConstantArg(object):
""" A constant argument or keyword argument to a Primitive. It is
independent of the block program structure. """
- def __init__(self, value, call_arg=True):
+ def __init__(self, value, call_arg=True, value_type=None):
+ """ call_arg -- call the value before returning it?
+ value_type -- the type of the value (from the TA type system). This
+ is useful to store e.g., the return type of call ASTs. """
self.value = value
self.call_arg = call_arg
+ self.value_type = value_type
def get(self, convert_to_ast=False):
""" If call_arg is True and the value is callable, call the value
@@ -1057,6 +932,14 @@ class ConstantArg(object):
else:
return self.value
+ def get_value_type(self):
+ """ If this ConstantArg has stored the type of its value, return
+ that. Else, use get_type(...) to guess the type of the value. """
+ if self.value_type is None:
+ return get_type(self.value)[0]
+ else:
+ return self.value_type
+
def __repr__(self):
return "ConstantArg(%s)" % (repr(self.value))
@@ -1113,7 +996,8 @@ def value_to_ast(value, *args_for_prim, **kwargs_for_prim):
# 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])
+ value.shade, value.gray],
+ return_type=TYPE_COLOR)
else:
raise ValueError("unknown type of raw value: " + repr(type(value)))
@@ -1121,7 +1005,9 @@ 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):
+ if not isinstance(ast_object, ast.AST):
+ return ast_object
+ elif isinstance(ast_object, ast.Num):
return ast_object.n
elif isinstance(ast_object, ast.Str):
return ast_object.s
@@ -1135,6 +1021,9 @@ def ast_to_value(ast_object):
else:
return None
+def ast_yield_true():
+ return ast.Yield(value=ast.Name(id='True', ctx=ast.Load))
+
def export_me(something):
""" Return True iff this is not a Primitive or its export_me attribute