def     confirmInteger(B):
    """
    the integer-domain value of B (if within comparison tolerance - else throw DOMAIN ERROR)
    """
    try:
        B = fuzzyInteger(B)

        if isinstance(B, int):
            return B
    except TypeError:
        pass

    assertError("DOMAIN ERROR") def     indexOrigin():
    """
    the current index origin (0 or 1)
    """
    return _IO.python() def     makeScalar(value, prototype=(0,)):
    """
    make an APL scalar quantity from a scalar Python value
    """
    if isinstance(value, aplQuantity):
        assertError("makeScalar: needs more thought")

    try:
        value = value.__iter__().__next__()
    except AttributeError:
        pass

    return aplQuantity((value,), None, prototype) def     makeVector(value, length=-1, prototype=(0,)):
    """
    make an APL vector quantity from a Python list
    """
    if length is None:
        length = 1

    if not isinstance(value, (tuple, lookAhead)):
        if length < 0 or prototype is None:
            value = lookAhead(value, 1)

    if isinstance(value, lookAhead):
        if length < 0:
            if value.buffered() == 0:
                length = 0

        if prototype is None:
            if value.buffered() == 0:
                assertError("ASSERTION ERROR: makeVector()")
            prototype = (makePrototype(value.peek()),)

    elif isinstance(value, list):
        value = tuple(value)

    if length == 0:
        return makeEmptyVector(prototype)

    if not isinstance(value, (tuple, lookAhead)):
        value = lookAhead(value, 1)

    return aplQuantity(value, length, None) def     makePrototype(value):
    """
    the 'array prototype' for this value
    """
    if isinstance(value, aplQuantity):
        prototype = value.makePrototype()

        return aplQuantity(prototype, len(prototype), prototype)

    return ' ' if isinstance(value, str) else 0 class   aplQuantity(object):
    """
    class that holds an APL quantity

    There a many query methods (most of them trivial) and very few behavioural methods.

    Methods may also alter the internal state.  This is because the state is lazy.
    """
    def __init__(self, value, dimension=0, prototype=(0,)):
        self._value = value
        self._dimension = dimension
        self._prototype = prototype
        self._resolved = False
        self._hash = None
        self.expressionToGo = None

    def __iter__(self):
        """
        required by the Python sort operation
        """
        return self._value.__iter__()

    def __eq__(self, other):
        """
        required by the Python sequence operations 'in' and 'not in'
        """
        if isinstance(other, aplQuantity):
            return self.__hash__() == other.__hash__()
        return False

    def __lt__(self, other):
        """
        required by the Python sort() method for sequences
        """
        return self._compare(other) < 0

    def __hash__(self):
        """
        required by the Python 'set' type
        """
        if self._hash is None:
            if self._resolved:
                for value in self._value:
                    if isinstance(value, aplQuantity):
                        value.__hash__()

            else:
                accumulator = []
                for value in self._value:
                    if isinstance(value, aplQuantity):
                        value.__hash__()
                    accumulator.append(value)

                if not isinstance(self._value, tuple):
                    self._value = tuple(accumulator)
                self._resolved = True

            self._hash = hash(self._value)
        return self._hash

    def _clone(self, other):
        """
        take on the attributes of some othe APL quantity (without realising any promises)
        """
        if isinstance(other, aplQuantity):
            # pylint: disable=protected-access
            self._value = other._value
            self._dimension = other._dimension
            self._prototype = other._prototype
            self._resolved = other._resolved
            self._hash = other._hash
            # pylint: enable=protected-access
        else:
            assertError("ASSERTION ERROR: aplQuantity._clone()")

        return self

    def _compare(self, other):
        """
        < 0, 0 or > 0 sor the self and other sort correctly
        """
        if not isinstance(other, aplQuantity):
            return 1

        delta = self.resolve().tally() - other.resolve().tally()

        if delta != 0:
            return delta

        A = self.__iter__()
        B = other.__iter__()
        try:
            while True:
                X = A.__next__()
                Y = B.__next__()

                if isinstance(X, aplQuantity):
                    delta = X.compare(Y)

                elif isinstance(Y, aplQuantity):
                    delta = -1

                elif isinstance(X, str) and isinstance(Y, str):
                    delta = ord(X) - ord(Y)

                elif isinstance(X, str):
                    delta = -1

                elif isinstance(Y, str):
                    delta = 1

                else:
                    delta = X - Y

                if delta != 0:
                    return delta

        except StopIteration:
            return 0

    def _lookAheadDimension(self, lowerBound):
        """
        return a good enough lower bound for the length of a vector
        """
        if isinstance(self._dimension, tuple):
            if True: # self._dimension[-1] == -1:
                assertError("DIMENSION ERROR: aplQuantity.lookAheadDimension()")

        dimension = self._dimension

        if dimension < 0:
            if isinstance(self._value, tuple):
                self._dimension = self.tally()

                dimension = self._dimension
            else:
                dimension = self._value.buffer(lowerBound)
        return dimension

    def depth(self):
        """
        the depth to which the quantity is nested

        not resolved so do not use internally
        """
        if self.isEmptyVector():
            return 1

        depth = reduce(lambda A, Y:
                       max(A, Y.depth() if isinstance(Y, aplQuantity) else 0),
                       self._value, 0)

        if self.isScalar() and depth == 0:
            return 0

        return 1 + depth

    def tally(self):
        """
        1 if a scalar, its length if a vector

        not resolved so do not use internally
        """
        if isinstance(self._dimension, tuple):
            if self._dimension[0] < 0:
                assertError("tally: needs more thought")
                if not isinstance(self._value, tuple):
                    self._value = tuple(self._value)
                self._dimension[0] = len(self._value)   ## wrong

                if self._dimension[0] == 0:
                    self._value = self._prototype
            return self._dimension[0]

        if isinstance(self._dimension, int):
            if self._dimension < 0:
                if not isinstance(self._value, tuple):
                    self._value = tuple(self._value)
                self._dimension = len(self._value)

                if self._dimension == 0:
                    self._value = self._prototype
            return self._dimension

        return 1

    def elementCount(self):
        """
        the dimension(s) of the quantity
        """
        if isinstance(self._dimension, tuple):
            if self._dimension[-1] < 0:
                assertError("DIMENSION ERROR: aplQuantity.elementCount()")

        if isinstance(self._dimension, tuple):
            return reduce(operator.mul, self._dimension, 1)

        elif isinstance(self._dimension, int):
            return self._dimension

        return 1

    def dimension(self):
        """
        the dimension(s) of the quantity
        """
        if isinstance(self._dimension, tuple):
            if self._dimension[-1] < 0:
                assertError("DIMENSION ERROR: aplQuantity.dimension()")

        return self._dimension

    def rank(self):
        """
        the rank of the quantity
        """
        if isinstance(self._dimension, tuple):
            return len(self._dimension)
        return None

    def prototype(self):
        """
        the quantity's array prototype used for padding/filling
        """
        if self._prototype is None:
            if isinstance(self._value, tuple):
                initial = self._value[0]
            else:
                initial = self._value.peek()

            self._prototype = (makeQuantity.makePrototype(initial),)
        return self._prototype

    def makePrototype(self):
        """
        the quantity's array prototype used for padding/filling
        """
        if self.isVectorLike():
            if isinstance(self._value, tuple):
                prototype = []
                for element in self._value:
                    prototype.append(makeQuantity.makePrototype(element))
            else:
                prototype = self._value.makePrototype()

            return tuple(prototype)

        assertNotArray(self, "WIP - makePrototype PROTOTYPE RANK ERROR")

    def padFill(self):
        """
        the pad (aka fill) character/number for a vector quantity
        """
        if self.isVectorLike():
            prototype = self.prototype()

            if isinstance(prototype, aplQuantity):
                return prototype
            return prototype[0]

        return self.prototype()

    def isString(self):
        """
        true if quantity really is a string
        """
        if self.padFill() == 0:
            return False

        if isinstance(self._value, tuple):
            for element in self._value:
                if not isinstance(element, str):
                    return False
            return True

        if not isinstance(self._value, lookAhead):
            self._value = lookAhead(self._value)

        return self._value.isString()

    def isScalar(self):
        """
        true if quantity is scalar
        """
        return self._dimension is None

    def isVector(self):
        """
        true if quantity is a vector
        """
        return isinstance(self._dimension, int)

    def isArray(self):
        """
        true if quantity is a higher order array
        """
        return isinstance(self._dimension, tuple)

    def isScalarLike(self):
        """
        true if quantity is scalar or a vector of length 1
        """
        if isinstance(self._dimension, int):
            return self._lookAheadDimension(2) == 1
        return self._dimension is None

    def isVectorLike(self):
        """
        true if quantity is scalar or a vector
        """
        if isinstance(self._dimension, int):
            return True
        return self._dimension is None

    def isEmptyVector(self):
        """
        true if quantity is a vector of length 0
        """
        if isinstance(self._dimension, int):
            return self._lookAheadDimension(1) == 0
        return False

    def isEmptyArray(self):
        """
        true if quantity is an array with a last axis of length 0
        """
        if isinstance(self._dimension, tuple):
            return self._dimension[-1] == 0
        return False

    def scalarToPy(self):
        """
        return Python scalar (a single number or character)
        """
        if self.isArray():
            assertError("ASSERTION ERROR: aplQuantity.scalarToPy()")

        return self._value.__iter__().__next__()

    def vectorToPy(self):
        """
        return Python sequence (or a promise thereof)
        """
        if self.isArray():
            assertError("ASSERTION ERROR: aplQuantity.vectorToPy()")

        if self.isEmptyVector():
            return ()

        return self._value

    def arrayToPy(self):
        """
        return the Python sequence (or a promise thereof) that represents an array
        """
        if self.isArray():
            return self._value

        assertError("ASSERTION ERROR: aplQuantity.arrayToPy()")

    def arrayByLastAxis(self):
        """
        return an APL array wrapped in a Last Axis iterator
        """
        if self.isArray():
            return lastAxisIterator(self._value, self.dimension()[-1])

        assertError("ASSERTION ERROR: aplQuantity.arrayByLastAxis()")

    def arrayByFirstAxis(self):
        """
        return an APL array wrapped in a First Axis iterator
        """
        if self.isArray():
            return firstAxisIterator(self._value, self.dimension()[-1])

        assertError("ASSERTION ERROR: aplQuantity.arrayByFirstAxis()")

    def promoteScalarToVectorPy(self):
        """
        return a scalar as a scalar iterator but a vector as a sequence (promise)
        """
        if self.isScalarLike():
            return self.scalarIterator()

        if self.isVector():
            return self.vectorToPy()

        assertError("ASSERTION ERROR: aplQuantity.promoteScalarToVectorPy()")

    def castToVectorPy(self):
        """
        return a Pyton vector (or promise thereof) regardless of the APL quantities type
        """
        if self.isScalar():
            return self.scalarIterator()

        if self.isEmptyVector():
            return self.scalarIterator()

        if self.isEmptyVector():
            return ()

        return self._value

    def python(self):
        """
        return the Python value (which could be a promise)
        """
        if self.isScalar():
            return self.scalarToPy()

        if self.isVector():
            return self.vectorToPy()

        assertError("ASSERTION ERROR: aplQuantity.python()")

    def safeScalar(self):
        """
        return the value of a scalar if safe and appropriate to do so

        We are just waiting for this one to blow up
        """
        if self.isScalar():
            value = self.scalarToPy()
            if not isinstance(value, aplQuantity):
                return value
            self._value = (value,)
        return self

    def scalarIterator(self):
        """
        infinite iterator for scalars so they may be used with arrays
        """
        return scalarIterator(self.scalarToPy(), -1, self.expressionToGo)

    def vectorIterator(self):
        """
        infinite iterator for vectors so they may be used with arrays
        """
        return vectorIterator(self.vectorToPy(), -1, self.expressionToGo)

    def resolve(self):
        """
        realise a promise (essentially a deep copy of a lazy APL quantity)
        """
        if not self._resolved:
            accumulator = []
            for value in self._value:
                if isinstance(value, aplQuantity):
                    value.resolve()
                accumulator.append(value)

            if not isinstance(self._value, tuple):
                self._value = tuple(accumulator)
            self._resolved = True
        return self class   aplIterator(object):
    """
    a base class to make it simpler to determine that an iterator is one of ours

    It is for now, and may always be, empty
    """
    pass class   lookAhead(object):
    """
    an iterator to allow peeking at the first few elements of some other iterator

    Extended to support other operations including buffering inside some other custom iterator
    """
    def __init__(self, B, N=0, F=None):
        self._B = B.__iter__()
        self._LA = []
        self._BL = 0

        if F is None:
            self.buffer(N)
        else:
            self.pushThisIn(F, N)

    def __iter__(self):
        return self

    def __next__(self):
        if self._BL == 0:
            return self._B.__next__()

        return self._popBuffer()

    def _popBuffer(self):
        """
        remove and return item from the front of the buffer
        """
        self._BL -= 1
        return self._LA.pop(0)

    def _pushBuffer(self, Y):
        """
        add item to the end of the buffer
        """
        self._LA.append(Y)
        self._BL += 1

        return Y

    def pushThisIn(self, F, N=1):
        """
        push F in at the end of buffer N times
        """
        while N > 0:
            N -= 1
            self._pushBuffer(F)

    def pushThenPop(self):
        """
        push another element into the end of the buffer, pop and return the element at the front

        When StopIteration is raised repeated calls do not drain the buffer.
        """
        if self._BL == 0:
            return self._B.__next__()

        self._pushBuffer(self._B.__next__())

        return self._popBuffer()

    def popThenPush(self):
        """
        pop (and return) the element at the front, push another element into the end of the buffer

        When StopIteration is raised repeated calls drain the buffer.
        """
        if self._BL == 0:
            return self._B.__next__()

        R = self._popBuffer()

        self._pushBuffer(self._B.__next__())

        return R

    def buffer(self, N):
        """
        if possible, ensure at least N elements are in the lookahead buffer
        """
        N -= self._BL

        try:
            while N > 0:
                N -= 1
                self._pushBuffer(self._B.__next__())
        except StopIteration:
            pass

        return self._BL

    def buffered(self):
        """
        how many elements are there in the lookahead buffer ?
        """
        return self._BL

    def peek(self):
        """
        return the first element in the lookahead buffer and leave the buffer unchanged
        """
        if self._BL == 0:
            try:
                return self._pushBuffer(self._B.__next__())
            except StopIteration:
                return None

        return self._LA[0]

    def makePrototype(self):
        """
        return the 'empty' array prototype for APL quantity enclosed in the buffer
        """
        P = []

        for Y in self._LA:
            P.append(makeQuantity.makePrototype(Y))

        try:
            while True:
                Y = self._pushBuffer(self._B.__next__())

                P.append(makeQuantity.makePrototype(Y))
        except StopIteration:
            pass

        return P

    def isString(self):
        """
        true if a real vector of characters, false if only a mixed vector
        """
        for Y in self._LA:
            if not isinstance(Y, str):
                return False

        try:
            while True:
                Y = self._pushBuffer(self._B.__next__())

                if not isinstance(Y, str):
                    return False
        except StopIteration:
            pass

        return True

    def isMatch(self, V):
        """
        true if V matches the contents of the buffer exactly, false otherwise
        """
        V = V.__iter__()

        try:
            for Y in self._LA:
                if Y != V.__next__():
                    return False
            V.__next__()
        except StopIteration:
            return True

        return False class   stack(object):
    """
    an iterator that allow a vector to be read backwards
    """
    def __init__(self, B):
        B = B.__iter__()

        T = []

        try:
            while True:
                T.insert(0, B.__next__())

        except StopIteration:
            pass

        self._B = T.__iter__()

    def __iter__(self):
        return self

    def __next__(self):
        return self._B.__next__() class   aplException(Exception):
    """
    A simple APL Exception Class
    """
    def __init__(self, message, expr=None):
        Exception.__init__(self)
        self.message = message
        self.expr = expr def     assertError(message):
    """
    raise an exception so aborting the current operation
    """
    raise aplException(message) def     assertTrue(condition, error):
    """
    raise error if condition is not true
    """
    if not condition:
        raise aplException(error)
import operator
from functools import reduce
from systemVariables import confirmInteger, indexOrigin
from makeQuantity import makeScalar, makeVector, makePrototype
from aplQuantity import aplQuantity
from aplIterators import aplIterator, lookAhead, stack
from aplError import aplException, assertError, assertTrue
    """
    implement dyadic ⍉ (for arrays - TBD)
    """
    return B

# ------------------------------

class   rotate(object):
    """
    the iterator for dyadic ⌽
    """
    def __init__(self, A, B):
        self._B = B.__iter__()
        self._L = lookAhead(self._B, A)

    def __iter__(self):
        return self

    def __next__(self):
        if self._B is None:
            return self._L.__next__()

        try:
            return self._B.__next__()

        except StopIteration:
            self._B = None

        return self.__next__()

# ------------------------------

class   partition(aplIterator):
    """
    the iterator for dyadic ⊂
    """
    def __init__(self, A, B):
        self._A = A.__iter__()
        self._B = B.__iter__()
        self._V = []
        self._I = 0

    def __iter__(self):
        return self

    def __next__(self):
        while True:
            try:
                X, Y = _nextPairWithInteger(self._A, self._B)
            except StopIteration:
                return self._aplVector(None)

            if X > self._I:
                self._I = X
                try:
                    return self._aplVector(Y)
                except StopIteration:
                    pass

            if X > 0:
                self._V.append(Y)
            elif X < 0:
                assertError("DOMAIN ERROR")

            self._I = X

    def _aplVector(self, Y):
        """
        create, for return, a non-empty APL vector quantity else raise StopIteration)
        """
        V = self._V

        if V == []:
            raise StopIteration

        self._V = [] if Y is None else [Y]

        return makeVector(tuple(V), len(V), (makePrototype(V[0]),))

# ------------------------------

class   membership(object):
    """
    the iterator for dyadic ∊
    """
    def __init__(self, A, B):
        self._A = A.__iter__()
        self._B = B.__iter__()
        self._S = set()

    def __iter__(self):
        return self

    def __next__(self):
        X = self._A.__next__()

        if X in self._S:
            return 1

        while not self._B is None:
            try:
                Y = self._B.__next__()
                self._S.add(Y)

                if X == Y:
                    return 1

            except StopIteration:
                self._B = None

        return 0

# ------------------------------

class   find(object):
    """
    the iterator for dyadic ⍷
    """
    def __init__(self, A, B):
        self._A = A

        self._B = lookAhead(B)