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     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     makeEmptyVector(prototype=(0,)):
    """
    make an empty APL vector quantity (⍬ or '')
    """
    return aplQuantity(prototype, 0, prototype) def     makeArray(value, dimensions, prototype=(0,)):
    """
    make an APL array quantity from a Python list
    """
    if isinstance(value, list):
        value = tuple(value)

    return aplQuantity(value, tuple(dimensions), prototype) 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   monadicTranspose(object):
    """
    iterator for the monadic transpose of an array - version for 2x2 matrices
    """
    def __init__(self, B, D):
        self._B = B             # the data
        self._RC = D[0]         # row count (first axis vector length)
        self._CC = D[-1]        # column count (last axis vector length)

        self._RO = 0            # row offset (current row)
        self._CO = 0            # column offset (current column)

        if self._CC <= 0:
            assertError("monadicTranspose: sheep dip")

    def __iter__(self):
        return self

    def __next__(self):
        if self._CO == self._CC:
            raise StopIteration

        S = self._RO * self._CC + self._CO

        self._RO += 1
        self._RO %= self._RC
        if self._RO == 0:
            self._CO += 1

        return self._B[S] def     assertTrue(condition, error):
    """
    raise error if condition is not true
    """
    if not condition:
        raise aplException(error) def     assertNotScalar(X, error="RANK ERROR"):
    """
    throw if quantity is a scalar
    """
    if X.isScalar():
        raise aplException(error) def     assertNotVector(X, error="RANK ERROR"):
    """
    throw if quantity is a vector
    """
    if X.isVector():
        raise aplException(error) def     assertNotArray(X, error="RANK ERROR"):
    """
    throw if quantity is a higher order array
    """
    if X.isArray():
        raise aplException(error)
import monadicIterators as iterator
from systemVariables import confirmInteger
from makeQuantity import makeScalar, makeVector, makeEmptyVector, makeArray
from aplQuantity import aplQuantity
from aplIterators import monadicTranspose
from aplError import assertTrue, assertNotScalar, assertNotVector, assertNotArray
    implement monadic ⍳

    the operand is scalar: there is no map as such
    """
    if B.isScalar():
        Bpy = confirmInteger(B.scalarToPy())

        assertTrue(Bpy >= 0, "DOMAIN ERROR")

        return makeVector(Fn(Bpy), Bpy)

    assertNotVector(B, "WIP - LENGTH ERROR")
    assertNotArray(B)

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

def     depth(_, B):
    """
    implement monadic ≡
    """
    return makeScalar(B.depth())

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

def     tally(_, B):
    """
    implement monadic ≢
    """
    return makeScalar(B.tally())

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

def     rho(_, B):
    """
    implement monadic ⍴
    """
    if B.isArray():
        return makeVector(B.dimension(), B.rank())

    if B.isVector():
        return makeVector((B.tally(),), 1)

    return makeEmptyVector()

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

def     unravel(_, B):
    """
    implement monadic ,
    """
    if B.isArray():
        return makeVector(B.arrayToPy(), B.elementCount(), B.prototype())

    if B.isVector():
        return B

    return makeVector(B.vectorToPy(), 1, B.prototype())

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

def     enlist(Fn, B):
    """
    implement monadic ∊
    """
    if B.isEmptyVector():
        return B

    if B.isVectorLike():
        return makeVector(Fn(B.vectorToPy()), -1, B.prototype())

    return makeVector(Fn(B.arrayToPy()), -1, B.prototype())

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

def     transpose(_, B):
    """
    implement monadic ⍉
    """
    if B.isArray():
        assertTrue(B.dimension() == (2, 2), "WIP - MATRIX ERROR")

        Rpy = monadicTranspose(B.arrayToPy(), B.dimension())

        return makeArray(Rpy, B.dimension(), None)

    return B

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

def     reverselast(Fn, B):
    """
    implement monadic ⌽
    """
    if B.isArray():
        assertTrue(B.dimension() == (2, 2), "WIP - MATRIX ERROR")

        Rpy = []
        for Bit in B.arrayByLastAxis():
            Rpy += Fn(Bit.vectorToPy())

        return makeArray(Rpy, B.dimension(), None)

    if B.isVector() and not B.isEmptyVector():
        return makeVector(Fn(B.vectorToPy()), B.dimension(), None)

    return B

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

def     reversefirst(Fn, B):
    """
    implement monadic ⊖
    """
    if B.isArray():
        assertTrue(B.dimension() == (2, 2), "WIP - MATRIX ERROR")

        Rpy = []
        for Bit in B.arrayByFirstAxis():
            Rpy += Fn(Bit.vectorToPy())


        Rpy = monadicTranspose(Rpy, B.dimension())