Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TurtleArt/tabasics.py10
-rw-r--r--TurtleArt/talogo.py4
-rw-r--r--TurtleArt/taprimitive.py213
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):