public interface Attendant {

    /**
     * Instructs the attendant to annotate the final report with the specified
     * message.
     * 
     * @param message
     *            The message to write to the final report.
     */
    void annotate(String message);

    /**
     * Gets the output from the Analytical Engine with annotations.
     * 
     * If this method is called before the program loaded in the Analytical
     * Engine has completed, then the returned report will reflect everything
     * that has been printed or annotated up to that point.
     * 
     * @return The annotated output from the Analytical Engine.
     */
    String finalReport();

    /**
     * Instructs the attendant to load the specified sequence of cards into the
     * card reader, thereby making them available to the Analytical Engine.
     * 
     * @param cards
     *            The cards representing the program that the Engine will run.
     * @throws BadCard
     *             if one of the cards is used incorrectly (that is, if there
     *             is a syntax error in the program specified by the card
     *             chain)
     * @throws IOException
     *             if the attendant fails to locate the cards requested from an
     *             "include" card.
     * @throws UnknownCard
     *             if a card specified in an "included" file has incorrect
     *             syntax.
     * @throws LibraryLookupException
     *             if a request to load a function from a library file fails.
     */
    void loadProgram(List<Card> cards)
            throws BadCard, IOException, UnknownCard, LibraryLookupException;

    /**
     * Act on a bell rung by the Analytical Engine.
     * 
     * A bell may be rung on the Engine if it encounters a
     * {@link analyticalengine.cards.CardType#BELL} card.
     * 
     * @param card The card that caused the bell to be rung.
     */
    void onBell(Card card);

    /**
     * Instructs the attendant to record the printed output from the Engine's
     * printer.
     * 
     * @param printed
     *            The output from the printer.
     */
    void receiveOutput(String printed);

    /**
     * Clears the current report and resets the card reader.
     */
    void reset();

    /**
     * Sets the card reader in which the attendant will load cards.
     * 
     * @param reader
     *            The device in which the attendant will load cards.
     */
    void setCardReader(CardReader reader);

    /**
     * Instructs the attendant to record output from the printer in a specific
     * format.
     * 
     * The currently accepted format string is specified (informally)
     * <a href="http://fourmilab.ch/babbage/cards.html#numeric">here</a>.
     * 
     * @param argument
     *            The format in which to write the ouput from the printer.
     */
    void setFormat(String argument);

    /**
     * Specifies the library to use when interpreting requests to include
     * built-in functions.
     * 
     * @param library
     *            The library containing built-in functions.
     */
    void setLibrary(Library library);

    /**
     * Instructs the attendant to remove comment cards when loading a program
     * into the card reader.
     * 
     * @param stripComments
     *            Whether to remove comment cards when loading a program.
     */
    void setStripComments(boolean stripComments);

    /**
     * Instructs the attendant to format the output from the printer either in
     * columns or in rows.
     * 
     * Writing in "rows" means automatically writing a new line after each
     * string received from the printer. Writing in "columns" means that a new
     * line will only be written in the final report when explicitly requested
     * by a "A write new line" card. (This is confusing, but this is how the
     * original code did it.)
     * 
     * @param direction
     *            The direction in which to write (rows or columns).
     */
    void writeInDirection(WriteDirection direction);

    /**
     * Instructs the attendant to write any following output from the printer
     * on a new line.
     */
    void writeNewline();
} public enum WriteDirection {
    /** Write in rows. */
    ROWS,
    /** Write in columns. */
    COLUMNS;
} public class BadCard extends CardException {

    /**
     * A default generated serial version UID.
     */
    private static final long serialVersionUID = -3311831632309378417L;

    /**
     * Instantiates this exception with the specified error message and the
     * specified card that caused the exception.
     * 
     * @param message
     *            The error message.
     * @param cause
     *            The card that caused this exception.
     */
    public BadCard(final String message, final Card cause) {
        super(message, cause);
    }

    /**
     * Instantiates this exception with the specified error message, card that
     * caused the exception, and exception that caused the exception.
     * 
     * @param message
     *            The error message.
     * @param cardCause
     *            The card that caused the exception.
     * @param throwableCause
     *            The throwable that caused this exception.
     */
    public BadCard(final String message, final Card cardCause,
            final Throwable throwableCause) {
        super(message, cardCause, throwableCause);
    }

} public class Bell extends CardException {

    /**
     * Default generated serial version UID.
     */
    private static final long serialVersionUID = 505374666147267503L;

    /**
     * Instantiates this exception with the specified error message and the
     * specified card that caused the exception.
     * 
     * @param message
     *            The error message.
     * @param cause
     *            The card that caused this exception.
     */
    public Bell(final String message, final Card cause) {
        super(message, cause);
    }

} public class Card {
    /**
     * Creates and returns a new comment card with the specified comment.
     * 
     * @param comment
     *            The comment to place on the card.
     * @return A new comment card containing the specified message.
     */
    public static Card commentCard(final String comment) {
        return new Card(CardType.COMMENT, new String[] { comment });
    }

    /**
     * Parses a card from the specified card string.
     * 
     * A card string is a single line of an Analytical Engine program.
     * 
     * @param cardString
     *            A string representation of a card.
     * @return The card specified by the given string.
     * @throws UnknownCard
     *             if the specified string does not correspond to a known card
     *             (essentially, this is a syntax error).
     */
    public static Card fromString(final String cardString) throws UnknownCard {
        if (cardString.isEmpty()) {
            return Card.commentCard("");
        }
        char firstChar = cardString.charAt(0);
        String untrimmedRest = cardString.substring(1);
        String rest = leftTrim(untrimmedRest.trim());
        // TODO need to account for possible inline comments after period
        switch (firstChar) {
        case '+':
            return new Card(CardType.ADD);
        case '*':
            return new Card(CardType.MULTIPLY);
        case '/':
            return new Card(CardType.DIVIDE);
        case '-':
            return new Card(CardType.SUBTRACT);
        case 'N':
            String[] args = rest.split("\\s+");
            for (int i = 0; i < args.length; i++) {
                args[i] = args[i].trim();
            }
            return new Card(CardType.NUMBER, args);
        case 'L':
            if (rest.endsWith("'")) {
                return new Card(CardType.LOADPRIME,
                        new String[] { rest.substring(0, rest.length() - 1) });
            }
            return new Card(CardType.LOAD, new String[] { rest });
        case 'Z':
            if (rest.endsWith("'")) {
                return new Card(CardType.ZLOADPRIME,
                        new String[] { rest.substring(0, rest.length() - 1) });
            }
            return new Card(CardType.ZLOAD, new String[] { rest });
        case 'S':
            if (rest.endsWith("'")) {
                return new Card(CardType.STOREPRIME,
                        new String[] { rest.substring(0, rest.length() - 1) });
            }
            return new Card(CardType.STORE, new String[] { rest });
        case '<':
            if (!rest.isEmpty()) {
                return new Card(CardType.LSHIFTN, new String[] { rest });
            }
            return new Card(CardType.LSHIFT);
        case '>':
            if (!rest.isEmpty()) {
                return new Card(CardType.RSHIFTN, new String[] { rest });
            }
            return new Card(CardType.RSHIFT);
        case 'C':
            return parseCombinatorialCard(cardString);
        case 'B':
            return new Card(CardType.BELL);
        case 'P':
            return new Card(CardType.PRINT);
        case 'H':
            return new Card(CardType.HALT);
        case 'D':
            if (rest.charAt(0) == '+') {
                return new Card(CardType.DRAW);
            } else if (rest.charAt(0) == '-') {
                return new Card(CardType.MOVE);
            } else if (rest.charAt(0) == 'X') {
                return new Card(CardType.SETX);
            } else if (rest.charAt(0) == 'Y') {
                return new Card(CardType.SETY);
            } else {
                throw new UnknownCard(
                        "Draw card must be one of DX, DY, D+, or D-, got: "
                                + cardString);
            }
        case 'A':
            if (rest.startsWith("include cards")) {
                return new Card(CardType.INCLUDE,
                        new String[] { rest.substring(14) });
            } else if (rest.startsWith("include from library cards for")) {
                return new Card(CardType.INCLUDELIB,
                        new String[] { rest.substring(31) });
            } else if (rest.startsWith("set decimal places to")) {
                return new Card(CardType.DECIMALEXPAND,
                        new String[] { rest.substring(22) });
            } else if (rest.startsWith("write numbers as")) {
                return new Card(CardType.WRITEPICTURE,
                        new String[] { untrimmedRest.substring(18) });
            } else if (rest.startsWith("write numbers with decimal point")) {
                return new Card(CardType.WRITEDECIMAL);
            } else if (rest.startsWith("write in rows")) {
                return new Card(CardType.WRITEROWS);
            } else if (rest.startsWith("write in columns")) {
                return new Card(CardType.WRITECOLUMNS);
            } else if (rest.startsWith("write new line")) {
                return new Card(CardType.NEWLINE);
            } else if (rest.startsWith("write annotation")) {
                return new Card(CardType.ANNOTATE,
                        new String[] { untrimmedRest.substring(18) });
            } else {
                throw new UnknownCard(
                        "Unknown attendant request: " + cardString);
            }
        case '(':
            if (!rest.isEmpty() && rest.charAt(0) == '?') {
                return new Card(CardType.CBACKSTART);
            }
            return new Card(CardType.BACKSTART);
        case ')':
            return new Card(CardType.BACKEND);
        case '{':
            if (!rest.isEmpty() && rest.charAt(0) == '?') {
                return new Card(CardType.CFORWARDSTART);
            }
            return new Card(CardType.FORWARDSTART);
        case '}':
            if (!rest.isEmpty() && rest.charAt(0) == '{') {
                return new Card(CardType.ALTERNATION);
            }
            return new Card(CardType.FORWARDEND);
        case 'T':
            if (rest.charAt(0) == '1') {
                return new Card(CardType.TRACEON);
            } else if (rest.charAt(0) == '0') {
                return new Card(CardType.TRACEOFF);
            } else {
                throw new UnknownCard(
                        "Expected T0 or T1 but got: " + cardString);
            }
        case ' ':
        case '.':
            return Card.commentCard(rest);
        default:
            throw new UnknownCard("Unable to parse: " + cardString);
        }
    }

    /**
     * Trim whitespace from the left of the specified string.
     * 
     * <a href=
     * "//fromdev.com/2009/07/playing-with-java-string-trim-basics.html">
     * Source.</a>
     * 
     * @param value
     *            The string to trim.
     * @return The string with any whitespace on the left end trimmed.
     */
    public static String leftTrim(final String value) {
        int i = 0;
        while (i < value.length() && Character.isWhitespace(value.charAt(i))) {
            i++;
        }
        return value.substring(i);
    }

    /**
     * Returns the combinatorial card represented by the specified string.
     * 
     * @param cardString
     *            The string representing a combinatorial card.
     * @return The card parsed from the specified string.
     * @throws UnknownCard
     *             if there is a syntax error while parsing the given string.
     */
    private static Card parseCombinatorialCard(final String cardString)
            throws UnknownCard {
        String rest = cardString.substring(1);
        if (rest.charAt(0) == 'F') {
            if (rest.charAt(1) == '?') {
                return new Card(CardType.CFORWARD,
                        new String[] { rest.substring(2) });
            } else if (rest.charAt(1) == '+') {
                return new Card(CardType.FORWARD,
                        new String[] { rest.substring(2) });
            } else {
                throw new UnknownCard(
                        "Forward card must be either CF+ or CF?, got: "
                                + cardString);
            }
        } else if (rest.charAt(0) == 'B') {
            if (rest.charAt(1) == '?') {
                return new Card(CardType.CBACKWARD,
                        new String[] { rest.substring(2) });
            } else if (rest.charAt(1) == '+') {
                return new Card(CardType.BACKWARD,
                        new String[] { rest.substring(2) });
            } else {
                throw new UnknownCard(
                        "Backward card must be either CB+ or CB?, got: "
                                + cardString);
            }
        } else {
            throw new UnknownCard(
                    "Combinatorial card must be either CF or CB, got: "
                            + cardString);
        }
    }

    /**
     * The arguments to the instruction, if any.
     * 
     * If the instruction does not require any arguments, this is an array of
     * length zero.
     */
    private final String[] arguments;

    /**
     * An additional comment provided on the card after the instruction and
     * arguments.
     */
    private final String comment;

    /** The specific instruction indicated by this card. */
    private final CardType type;

    /**
     * Creates a new card of the specified type with no arguments and no
     * additional comments.
     * 
     * @param type
     *            The type of the card.
     */
    public Card(final CardType type) {
        this(type, new String[] {});
    }

    /**
     * Creates a new card of the specified type with the specified comment, but
     * no arguments.
     * 
     * @param type
     *            The type of the card.
     * @param comment
     *            The comment on the card.
     */
    public Card(final CardType type, final String comment) {
        this(type, new String[] {}, comment);
    }

    /**
     * Creates a new card of the specified type with the specified arguments
     * but no additional comments.
     * 
     * @param type
     *            The type of the card.
     * @param arguments
     *            The arguments to the instruction that this card represents.
     */
    public Card(final CardType type, final String[] arguments) {
        this(type, arguments, "");
    }

    /**
     * Creates a new card of the specified type with the specified arguments
     * and specified additional comments.
     * 
     * @param type
     *            The type of the card.
     * @param arguments
     *            The arguments to the instruction that this card represents.
     * @param comment
     *            An additional comment on the card that doesn't affect its
     *            behavior.
     */
    public Card(final CardType type, final String[] arguments,
            final String comment) {
        this.type = type;
        this.arguments = arguments.clone();
        this.comment = comment;
    }

    /**
     * Returns the argument at the specified index.
     * 
     * Calling code must ensure that the index is less than the number of
     * arguments.
     * 
     * @param i
     *            The index of the argument to return in the array of all
     *            arguments.
     * @return The argument at index {@code i}
     */
    public String argument(final int i) {
        return this.arguments[i];
    }

    /**
     * Returns any additional comment provided when read from the input.
     * 
     * If no comment was provided, this method returns the empty string.
     * 
     * If the type of this card is
     * {@link analyticalengine.cards.CardType#COMMENT}, then this method
     * returns the empty string; on such a card, the comment is provided as the
     * first (and only) argument.
     * 
     * @return The additional comment, if any, provided on the card.
     */
    public String comment() {
        return this.comment;
    }

    /**
     * Returns the number of arguments of a card of this type.
     * 
     * @return The number of arguments of a card of this type.
     */
    public int numArguments() {
        return this.type.numArguments();
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        if (this.numArguments() > 0) {
            return this.type + Arrays.toString(this.arguments);
        }
        return this.type.toString();
    }

    /**
     * Returns the string representation of this card (with any terminal
     * comments intact).
     * 
     * @return The string representation of the specified card.
     * @throws UnknownCard
     *             if this card is of an unrecognized type.
     */
    public String toText() throws UnknownCard {
        // TODO account for comments after the instructions
        switch (this.type()) {
        case ADD:
            return "+";
        case ALTERNATION:
            return "}{";
        case ANNOTATE:
            return "A write annotation " + this.argument(0);
        case BACKEND:
            return ")";
        case BACKSTART:
            return "(";
        case BACKWARD:
            return "CB+" + this.argument(0);
        case BELL:
            return "B";
        case CBACKSTART:
            return "(?";
        case CBACKWARD:
            return "CB?" + this.argument(0);
        case CFORWARD:
            return "CF?" + this.argument(0);
        case CFORWARDSTART:
            return "{?";
        case COMMENT:
            return "  " + this.argument(0);
        case DECIMALEXPAND:
            return "A set decimal places to " + this.argument(0);
        case DIVIDE:
            return "/";
        case DRAW:
            return "D+";
        case FORWARD:
            return "CF+" + this.argument(0);
        case FORWARDEND:
            return "}";
        case FORWARDSTART:
            return "{";
        case HALT:
            return "H";
        case INCLUDE:
            return "A include cards " + this.argument(0);
        case INCLUDELIB:
            return "A include from library cards for " + this.argument(0);
        case LOAD:
            return "L" + this.argument(0);
        case LOADPRIME:
            return "L" + this.argument(0) + ";";
        case LSHIFT:
            return "<";
        case LSHIFTN:
            return "<" + this.argument(0);
        case MOVE:
            return "D-";
        case MULTIPLY:
            return "*";
        case NEWLINE:
            return "A write new line";
        case NUMBER:
            return "N" + this.argument(0) + " " + this.argument(1);
        case PRINT:
            return "P";
        case RSHIFT:
            return ">";
        case RSHIFTN:
            return ">" + this.argument(0);
        case SETX:
            return "DX";
        case SETY:
            return "DY";
        case STORE:
            return "S" + this.argument(0);
        case STOREPRIME:
            return "S" + this.argument(0) + "'";
        case SUBTRACT:
            return "-";
        case TRACEOFF:
            return "T0";
        case TRACEON:
            return "T1";
        case WRITECOLUMNS:
            return "A write in columns";
        case WRITEDECIMAL:
            return "A write numbers with decimal point";
        case WRITEPICTURE:
            return "A write numbers as " + this.argument(0);
        case WRITEROWS:
            return "A write in rows";
        case ZLOAD:
            return "Z" + this.argument(0);
        case ZLOADPRIME:
            return "Z" + this.argument(0) + "'";
        default:
            throw new UnknownCard("Unable to parse: " + this);
        }
    }

    /**
     * Returns the type of instruction this card represents.
     * 
     * @return The type of instruction this card represents.
     */
    public CardType type() {
        return this.type;
    }
} public class Halt extends CardException {

    /**
     * Default generated serial version UID.
     */
    private static final long serialVersionUID = -8026821124810632777L;

    /**
     * Instantiates this exception with the specified error message.
     * 
     * @param message
     *            The error message.
     */
    public Halt(final String message) {
        super(message);
    }

    /**
     * Instantiates this exception with the specified error message and the
     * specified exception that caused the exception.
     * 
     * @param message
     *            The error message.
     * @param cause
     *            The throwable that caused this exception.
     */
    public Halt(final String message, final Throwable cause) {
        super(message, cause);
    }

    /**
     * Instantiates this exception with the specified error message and the
     * specified card that caused the exception.
     * 
     * @param message
     *            The error message.
     * @param cause
     *            The card that caused this exception.
     */
    public Halt(final String message, final Card cause) {
        super(message, cause);
    }

} public interface CardReader {

    /**
     * Make the card {@code n} cards ahead of the current card to be the next
     * card read.
     * 
     * @param n
     *            The number of cards by which to advance the index of the
     *            current card.
     * @throws IndexOutOfBoundsException
     *             if the specified advance amount would rewind the card reader
     *             beyond the end of the card chain.
     */
    void advance(int n);

    /**
     * Provides an unmodifiable view of the cards loaded in the reader.
     * 
     * @return An unmodifiable view of the cards loaded in the reader.
     */
    List<Card> cards();

    /**
     * Make the specified card chain available to the card reader.
     * 
     * @param cardChain
     *            The sequence of cards to load.
     */
    void mountCards(List<Card> cardChain);

    /**
     * Reads the current card in the card chain and prepares the following card
     * to be read on the next invocation of this method.
     * 
     * @return The next card in the card chain.
     * @throws Halt
     *             if there are no more cards to read in the card chain.
     */
    Card readAndAdvance() throws Halt;

    /**
     * Make the card {@code n} cards behind the current card to be the next
     * card read.
     * 
     * @param n
     *            The number of cards by which to decrease the index of the
     *            current card.
     * @throws IndexOutOfBoundsException
     *             if the specified reverse amount would rewind the card reader
     *             beyond the beginning of the card chain.
     */
    void reverse(int n);

    /**
     * Removes the currently mounted card chain from the card reader.
     */
    void unmountCards();

} public interface CurvePrinter {
    /**
     * Sets the x coordinate of the plotting pen.
     * 
     * @param x
     *            The x coordinate of the plotting pen.
     */
    void setX(BigInteger x);

    /**
     * Sets the y coordinate of the plotting pen.
     * 
     * @param y
     *            The y coordinate of the plotting pen.
     */
    void setY(BigInteger y);

    /**
     * Draws to the current location specified by the x and y coordinates.
     */
    void draw();

    /**
     * Moves the pen (without drawing) to the current location specified by the
     * x and y coordinates.
     */
    void move();

    /**
     * Resets the position of the pen to the origin and clears the current
     * plot.
     */
    void reset();
} public interface Mill {

    /**
     * Whether the most recent arithmetic operation resulted in an either an
     * overflow or a sign change.
     * 
     * There is no way to know which occurred without also knowing the
     * operation and operands.
     * 
     * @return {@code true} if and only if an overflow or sign change occurred
     *         when computing the result of the most recent operation.
     */
    boolean hasRunUp();

    /**
     * Clears the run up lever, currently set operation, and ingress and egress
     * axes.
     */
    void reset();

    /**
     * Performs a left shift on the ingress axes by the specified number of
     * digits.
     * 
     * This method shifts the ingress axes only because a left shift is
     * performed before a floating point division operation.
     * 
     * This method makes the return value of {@link #mostRecentValue()} to be
     * the value stored in the main egress axis (after the shift).
     * 
     * @param shift
     *            The number of digits by which to shift to the left.
     * @throws IllegalArgumentException
     *             if the specified shift value is negative, or would certainly
     *             cause an overflow.
     */
    void leftShift(int shift);

    /**
     * Performs a right shift on the egress axes by the specified number of
     * digits.
     * 
     * This method shifts the egress axes only because a right shift is
     * performed after a floating point multiplication operation.
     * 
     * This method makes the return value of {@link #mostRecentValue()} to be
     * the value stored in the main egress axis (after the shift).
     * 
     * @param shift
     *            The number of digits by which to shift to the right.
     * @throws IllegalArgumentException
     *             if the specified shift value is negative, or would certainly
     *             cause an overflow.
     */
    void rightShift(int shift);

    /**
     * Specifies the operation to perform on the next two integers loaded into
     * the mill.
     * 
     * @param operation
     *            The operation to be performed.
     */
    void setOperation(Operation operation);

    /**
     * Transfers the specified value into one of the main ingress axes.
     * 
     * The currently available ingress axis switches implicitly each time this
     * method is called. For example, the first invocation sets the first
     * ingress axis and the second sets the second ingress axis. After that,
     * the first ingress axis will be the next one available, and the pattern
     * repeats.
     * 
     * When the first ingress axis is set, the prime axis is reset to zero.
     * 
     * This is a convenience method for
     * {@link #transferIn(BigInteger, boolean)} where the second argument is
     * {@code false}.
     * 
     * This method makes the return value of {@link #mostRecentValue()} to be
     * the value stored in the main ingress axis (after the transfer).
     * 
     * @param value
     *            The value to load into the currently available ingress axis.
     * @throws IllegalArgumentException
     *             if the value is too large or too small to be loaded into the
     *             mill.
     */
    void transferIn(BigInteger value);

    /**
     * Transfers the specified value into one of the main ingress axes, or the
     * prime ingress axis if {@code prime} is {@code true}.
     * 
     * Unless the second argument is {@code true}, the currently available
     * ingress axis switches implicitly each time this method is called. For
     * example, the first invocation sets the first ingress axis and the second
     * sets the second ingress axis. After that, the first ingress axis will be
     * the next one available, and the pattern repeats.
     * 
     * When the first ingress axis is set, the prime axis is reset to zero.
     * 
     * This method makes the return value of {@link #mostRecentValue()} to be
     * the value stored in the main ingress axis (after the shift).
     * 
     * @param value
     *            The value to load into the currently available ingress axis.
     * @param prime
     *            If this is {@code true} the prime ingress axis is set instead
     *            of one of the main ingress axes.
     * @throws IllegalArgumentException
     *             if the value is too large or too small to be loaded into the
     *             mill.
     */
    void transferIn(BigInteger value, boolean prime);

    /**
     * Returns the result of the most recently computed arithmetic operation
     * (that is, the value stored in the main egress axis).
     * 
     * This is a convenience method for {@link #transferOut(boolean)} with an
     * argument of {@code false}
     * 
     * @return The value stored in the main egress axis.
     */
    BigInteger transferOut();

    /**
     * Returns the result of the most recently computed arithmetic operation.
     * 
     * If {@code prime} is true, this returns the value stored in the prime
     * egress axis. Otherwise, it returns the value stored in the main egress
     * axis.
     * 
     * @param prime
     *            Whether to return the value of the prime egress axis.
     * @return The value stored in the main egress axis, or the prime egress
     *         axis if {@code prime} is {@code true}.
     */
    BigInteger transferOut(boolean prime);

    /**
     * The "most recently used" value, which may be the most recently
     * transferred value (in or out), the result of a computation, or the
     * result of a shift.
     * 
     * @return The "most recently used" integer, or an empty {@link Optional}
     *         if no such integer exists.
     */
    Optional<BigInteger> mostRecentValue();
} public enum Operation {
    /** Add two integers. */
    ADD,
    /** Subtract two integers. */
    SUBTRACT,
    /** Multiply two integers. */
    MULTIPLY,
    /** Divide two integers. */
    DIVIDE;
} public interface Printer {
    /**
     * Converts the specified integer to a string.
     * 
     * If {@code value} is {@code null}, then the printer may print the empty
     * string.
     * 
     * @param value
     *            The value to print.
     * @return The string representing the printed integer.
     */
    String print(BigInteger value);
} public interface Store {
    /**
     * Retrieves and returns the value stored at {@code address}.
     * 
     * @param address
     *            A memory location.
     * @return The value stored at the memory location.
     * @throws IndexOutOfBoundsException
     *             if the address is negative or too large for the store.
     */
    BigInteger get(int address);

    /**
     * Stores {@code value} in the memory location specified by {@code address}
     * .
     * 
     * @param address
     *            A memory location.
     * @param value
     *            A value to store in {@code address}.
     * @throws IndexOutOfBoundsException
     *             if the address is negative or too large for the store.
     */
    void put(int address, BigInteger value);

    /**
     * Clears all values in the store.
     */
    void reset();
}
import analyticalengine.cards.Card;
import analyticalengine.cards.Halt;
import analyticalengine.components.CardReader;
import analyticalengine.components.CurvePrinter;
import analyticalengine.components.Mill;
import analyticalengine.components.Operation;
import analyticalengine.components.Printer;
import analyticalengine.components.Store;
import java.math.BigInteger;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import analyticalengine.attendant.Attendant;
import analyticalengine.attendant.WriteDirection;
import analyticalengine.cards.BadCard;
import analyticalengine.cards.Bell;
/**
 * DefaultAnalyticalEngine.java - basic implementation of analytical engine
 * 
 * Copyright 2014-2016 Jeffrey Finkelstein.
 * 
 * This file is part of analyticalengine.
 * 
 * analyticalengine is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 * 
 * analyticalengine is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * analyticalengine. If not, see <http://www.gnu.org/licenses/>.
 */
package analyticalengine;




/**
 * A basic implementation of the Analytical Engine interface.
 * 
 * @author Jeffrey Finkelstein &lt;jeffrey.finkelstein@gmail.com&gt;
 * @since 0.0.1
 */
public class DefaultAnalyticalEngine implements AnalyticalEngine {

    /**
     * The logger for this class.
     */
    private static final transient Logger LOG = LoggerFactory
            .getLogger(DefaultAnalyticalEngine.class);

    /** The attendant that operates the Analytical Engine. */
    private Attendant attendant = null;