diff options
-rw-r--r-- | TurtleArt/tabasics.py | 10 | ||||
-rw-r--r-- | TurtleArt/talogo.py | 4 | ||||
-rw-r--r-- | TurtleArt/taprimitive.py | 213 |
3 files changed, 174 insertions, 53 deletions
diff --git a/TurtleArt/tabasics.py b/TurtleArt/tabasics.py index 8a11370..2ab9059 100644 --- a/TurtleArt/tabasics.py +++ b/TurtleArt/tabasics.py @@ -95,7 +95,7 @@ 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 (ArgSlot, ConstantArg, Primitive) +from taprimitive import (ArgSlot, ConstantArg, or_, Primitive) from tatype import * # TODO reduce to necessary objects from taturtle import Turtle @@ -654,8 +654,12 @@ tasetshade :shade \n') help_string=_('adds two alphanumeric inputs')) self.tw.lc.def_prim('plus', 2, # TODO re-enable use with lists - Primitive(Primitive.plus, slot_wrappers={ - (0, 2): Primitive(Primitive.convert_for_plus)})) + # add up two numbers ... + or_(Primitive(Primitive.plus, return_type=TYPE_NUMBER, + arg_descs=[ArgSlot(TYPE_NUMBER), ArgSlot(TYPE_NUMBER)]), + # ... or concatenate two strings + Primitive(Primitive.plus, return_type=TYPE_STRING, + arg_descs=[ArgSlot(TYPE_STRING), ArgSlot(TYPE_STRING)]))) palette.add_block('minus2', style='number-style-porch', diff --git a/TurtleArt/talogo.py b/TurtleArt/talogo.py index ef482a1..8eae9a1 100644 --- a/TurtleArt/talogo.py +++ b/TurtleArt/talogo.py @@ -468,7 +468,9 @@ class LogoCode: self.tw.showblocks() self.tw.display_coordinates() raise logoerror("#noinput") - call_args = type(self.cfun.fcn).__name__ != 'Primitive' + is_Primitive = type(self.cfun.fcn).__name__ == 'Primitive' + is_PrimitiveDisjunction = type(self.cfun.fcn).__name__ == 'PrimitiveDisjunction' + call_args = not (is_Primitive or is_PrimitiveDisjunction) for i in range(token.nargs): self._no_args_check() self.icall(self._eval, call_args) diff --git a/TurtleArt/taprimitive.py b/TurtleArt/taprimitive.py index 3a85984..a8b0fe7 100644 --- a/TurtleArt/taprimitive.py +++ b/TurtleArt/taprimitive.py @@ -266,20 +266,43 @@ class Primitive(object): 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 + + if isinstance(new_prim.arg_descs, ArgListDisjunction): + slot_list_alternatives = list(new_prim.arg_descs) + else: + slot_list_alternatives = [new_prim.arg_descs] + + # arguments + error = None + for slot_list in slot_list_alternatives: + list_failed = False + new_slot_list = [] + fillers = list(arguments[:]) + for slot in slot_list: + if isinstance(slot, ArgSlot): + f = fillers.pop(0) + try: + value = slot.fill(f) + except TATypeError as error: + list_failed = True + break + else: + new_slot_list.append(ConstantArg(value)) + else: + new_slot_list.append(slot) + if not list_failed: + new_prim.arg_descs = new_slot_list + break + if error is not None: + raise error + + # keyword arguments 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): @@ -904,6 +927,52 @@ class Primitive(object): return arg1 > arg2 +class Disjunction(tuple): + """ Abstract disjunction class (not to be instantiated directly) """ + + def __repr__(self): + s = ["("] + for disj in self: + s.append(repr(disj)) + s.append(" or ") + s.pop() + s.append(")") + return "".join(s) + + def get_alternatives(self): + """ Return a tuple of slot alternatives, i.e. self """ + return self + + +class PrimitiveDisjunction(Primitive,Disjunction): + """ Disjunction of two or more Primitives """ + + def __call__(self, *runtime_args, **runtime_kwargs): + """ TODO doc """ + + # remove the first argument if it is a LogoCode instance + if runtime_args and isinstance(runtime_args[0], LogoCode): + runtime_args = runtime_args[1:] + + for prim in self: + try: + new_prim = prim.fill_slots(*runtime_args, **runtime_kwargs) + except TATypeError: + # on failure, try the next one + continue + else: + # on success, call this Primitive + return new_prim() + + # if we get here, all disjuncts failed + raise TATypeError("all Primitives failed") # TODO better error message + + +class ArgListDisjunction(Disjunction): + """ Disjunction of two or more argument lists """ + pass + + class ArgSlot(object): """ Description of the requirements that a Primitive demands from an argument or keyword argument. An ArgSlot is filled at runtime, based @@ -927,59 +996,97 @@ class ArgSlot(object): return "ArgSlot(type=%s, call=%s, wrapper=%s)" % (repr(self.type), repr(self.call_arg), repr(self.wrapper)) + def get_alternatives(self): + """ Return a tuple of slot alternatives, i.e. (self, ) """ + return (self, ) + 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: + if isinstance(argument, ast.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) + for slot in self.get_alternatives(): + + # check if the argument can fill this slot (type-wise) + if slot.wrapper is not None: + arg_type = get_type(slot.wrapper)[0] else: + arg_type = get_type(argument)[0] + converter = get_converter(arg_type, slot.type) + if converter is None: + continue + + # 1. Call the argument? + called_argument = argument + (func, args) = (None, []) + if (isinstance(argument, tuple) and argument + and callable(argument[0])): + func = argument[0] + args = argument[1:] + elif callable(argument): + func = argument + if func is not None: if convert_to_ast: - argument = ast.Lambda(args=[], vararg=None, kwarg=None, - defaults=[], body=call_ast) + call_ast = get_call_ast(func.__name__, + [value_to_ast(arg) for arg in args]) + if slot.call_arg: + was_argument_called = True + if convert_to_ast: + called_argument = call_ast + else: + called_argument = func(*args) else: - if isinstance(func, Primitive): - argument = func.fill_slots(*args) + if convert_to_ast: + was_argument_called = True + called_argument = ast.Lambda(args=[], vararg=None, + kwarg=None, defaults=[], body=call_ast) + elif args: + was_argument_called = True + if isinstance(func, Primitive): + called_argument = func.fill_slots(*args) + else: + called_argument = Primitive(func, + [ConstantArg(arg) for arg in args]) + + # 2. apply any wrappers + wrapped_argument = called_argument + if slot.wrapper is not None: + if convert_to_ast: + if hasattr(slot.wrapper, "get_ast"): + wrapped_argument = slot.wrapper.get_ast( + called_argument) else: - argument = Primitive(func, - [ConstantArg(arg) for arg in args]) + raise PyExportError(("cannot convert callable %s to " + "an AST") % (repr(slot.wrapper))) + elif callable(slot.wrapper): + wrapped_argument = slot.wrapper(called_argument) - # 2. apply any wrappers - if convert_to_ast: - if hasattr(self.wrapper, "get_ast"): - argument = self.wrapper.get_ast(argument) + # 3. check the type and convert the argument if necessary + try: + converted_argument = convert(wrapped_argument, slot.type, + converter=converter) + except TATypeError as ce: + # on failure, try the next slot + continue else: - raise PyExportError("cannot convert callable %s to an AST" - % (repr(self.wrapper))) - elif callable(self.wrapper): - argument = self.wrapper(argument) + # on success, return the result + return converted_argument - # 3. check the type and convert the argument if necessary - try: - argument = convert(argument, self.type) - except TATypeError: - traceback.print_exc() - # TODO find appropriate logoerror message depending on type combination - raise logoerror("type problem") + # if we haven't returned anything yet, then all alternatives failed + traceback.print_exc() + # TODO find appropriate logoerror message depending on type combination + raise TATypeError("type mismatch while filling arg slot") return argument +class ArgSlotDisjunction(ArgSlot,Disjunction): + """ Disjunction of two or more argument slots """ + pass + + class ConstantArg(object): """ A constant argument or keyword argument to a Primitive. It is independent of the block program structure. """ @@ -1001,10 +1108,18 @@ class ConstantArg(object): return "ConstantArg(%s)" % (repr(self.value)) -class Or(tuple): - """ Disjunction of two or more arguments or argument types """ - pass - +def or_(*disjuncts): + """ Return a disjunction object of the same type as the disjuncts. If + the item type cannot be linked to a Disjunction class, return a tuple + of the disjuncts. """ + if isinstance(disjuncts[0], Primitive): + return PrimitiveDisjunction(disjuncts) + elif isinstance(disjuncts[0], (list, ArgListDisjunction)): + return ArgListDisjunction(disjuncts) + elif isinstance(disjuncts[0], ArgSlot): + return ArgSlotDisjunction(disjuncts) + else: + return tuple(disjuncts) def value_to_ast(value, *args_for_prim, **kwargs_for_prim): |