diff options
author | Marion <marion.zepf@gmail.com> | 2013-09-04 15:08:07 (GMT) |
---|---|---|
committer | Marion <marion.zepf@gmail.com> | 2013-09-04 15:08:07 (GMT) |
commit | 97b945e1e088c2e51d9a6233be49c401040883e9 (patch) | |
tree | b9ae9cb56638f9bf7291b5b762f7ce942e7cc32b /TurtleArt/taprimitive.py | |
parent | cc420f0e37c82275bb5d6b46d85aef9cb9fdae91 (diff) | |
parent | e0fcb7656caf8d35e3e4bfa9ce99729328f35b1d (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.py | 239 |
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 |