public interface AnalyticalEngine {

    /**
     * Sets the attendant, who operates the Analytical Engine.
     * 
     * @param attendant
     *            The operator of the Analytical Engine.
     */
    void setAttendant(Attendant attendant);

    /**
     * Sets the mill, which performs the arithmetic logic.
     * 
     * @param mill
     *            The arithmetic logic unit of the Engine.
     */
    void setMill(Mill mill);

    /**
     * Sets the store, which is the memory of the Engine.
     * 
     * @param store
     *            The memory of the Engine.
     */
    void setStore(Store store);

    /**
     * Sets the card reader, which provides the program for the Engine.
     * 
     * @param reader
     *            The device which provides the program for the Engine.
     */
    void setCardReader(CardReader reader);

    /**
     * Sets the printer, which produces the textual output from the Engine.
     * 
     * @param printer
     *            The device which produces the textual output from the Engine.
     */
    void setPrinter(Printer printer);

    /**
     * Sets the curve printer, which outputs plots of curves.
     * 
     * @param printer
     *            The device which plots curves.
     */
    void setCurvePrinter(CurvePrinter printer);

    /**
     * Resets each of the components of the engine to their initial states.
     * 
     * This includes clearing the store and resetting the mill.
     */
    void reset();

    /**
     * Runs the program specified by the card chain in the card reader.
     * 
     * This method sequentially executes each card in the card chain, alerting
     * the attendant if his or her action is required.
     * 
     * The mill, store, card reader, printer, and attendant properties must all
     * be set before invoking this method. Furthermore, the attendant must have
     * loaded a program into the card reader before invoking this method.
     * 
     * @throws BadCard
     *             if the Engine encounters an invalid card as it is processing
     *             the card chain.
     */
    void run() throws BadCard;
} 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;

    /** The device that maintains the sequence of cards being read. */
    private CardReader cardReader = null;

    /** The device that plots curves as output. */
    private CurvePrinter curvePrinter = null;

    /** The mill that performs the arithmetic logic for the Engine. */
    private Mill mill = null;

    /** The device that prints numbers as output. */
    private Printer printer = null;

    /** The memory for the Engine. */
    private Store store = null;

    /**
     * Performs the advance or reverse specified by the given card.
     * 
     * This includes conditional and unconditional advances and reverses.
     * 
     * @param card
     *            A combinatorial card.
     * @throws IllegalArgumentException
     *             if the card is not a combinatorial card.
     */
    private void applyCombinatorialCard(final Card card) {
        int numCards = Integer.parseInt(card.argument(0));
        boolean hasRunUp = this.mill.hasRunUp();
        switch (card.type()) {
        case CBACKWARD:
            if (hasRunUp) {
                LOG.debug("Reversing (due to run-up) by {} cards", numCards);
                this.cardReader.reverse(numCards);
            } else {
                LOG.debug("Skipped reversing due to no run-up");
            }
            break;
        case CFORWARD:
            if (hasRunUp) {
                LOG.debug("Advancing (due to run-up) by {} cards", numCards);
                this.cardReader.advance(numCards);
            } else {
                LOG.debug("Skipped advancing due to no run-up");
            }
            break;
        case FORWARD:
            LOG.debug("Advancing card reader by {} cards", numCards);
            this.cardReader.advance(numCards);
            break;
        case BACKWARD:
            LOG.debug("Reversing by {} cards", numCards);
            this.cardReader.reverse(numCards);
            break;
        default:
            throw new IllegalArgumentException(
                    "Expected combinatorial card, not " + card);
        }
    }

    /**
     * Performs the instruction specified by the given card.
     * 
     * @param card
     *            The card containing the instruction for the Engine.
     * @throws Bell
     *             if the card indicates that a bell on the Analytical Engine
     *             should be rung.
     * @throws Halt
     *             if the card indicates that the Engine should halt execution
     *             immediately.
     * @throws BadCard
     *             If the specified card has invalid syntax.
     */
    private void executeCard(final Card card) throws Bell, Halt, BadCard {
        switch (card.type()) {
        // Instructions affecting the mill: arithmetic and shift instructions
        case ADD:
        case DIVIDE:
        case MULTIPLY:
        case SUBTRACT:
            this.setOperation(card);
            break;
        case RSHIFTN:
        case LSHIFTN:
            this.setShift(card);
            break;
        // Control flow instructions: advance and reverse instructions
        case CBACKWARD:
        case CFORWARD:
        case BACKWARD:
        case FORWARD:
            this.applyCombinatorialCard(card);
            break;
        // Memory access instructions: store, load, and zero-load
        case LOAD:
        case LOADPRIME:
        case NUMBER:
        case STORE:
        case STOREPRIME:
        case ZLOAD:
        case ZLOADPRIME:
            this.handleMemoryAccess(card);
            break;
        // Curve printer instructions
        case DRAW:
        case MOVE:
        case SETX:
        case SETY:
            this.handleCurveDrawing(card);
            break;
        // Action instructions: halt, ring bell, or print
        case BELL:
        case HALT:
        case PRINT:
            this.handleActionCard(card);
            break;
        // Attendant action instructions
        case ANNOTATE:
        case NEWLINE:
        case WRITECOLUMNS:
        case WRITEROWS:
        case WRITEPICTURE:
            this.handleAttendantAction(card);
            break;
        // Debugging instructions
        case COMMENT:
        case TRACEON:
        case TRACEOFF:
            this.handleDebuggingCard(card);
            break;
        case ALTERNATION:
        case BACKEND:
        case BACKSTART:
        case DECIMALEXPAND:
        case CBACKSTART:
        case CFORWARDSTART:
        case FORWARDEND:
        case FORWARDSTART:
        case INCLUDE:
        case INCLUDELIB:
        case LSHIFT:
        case RSHIFT:
        case WRITEDECIMAL:
            // these should all be removed by attendant before loading cards
            throw new BadCard("Attendant failed to remove card", card);
        default:
            break;
        }
    }

    /**
     * Performs the Analytical Engine action specified by the given card.
     * 
     * This includes ringing the bell to attract the attention of the
     * attendant, halting the machine, and sending a value from the mill to the
     * printer.
     * 
     * @param card
     *            The action to perform.
     * @throws Bell
     *             if the card indicates the bell should be rung.
     * @throws Halt
     *             if the card indicates the machine should halt.
     * @throws IllegalArgumentException
     *             if the card is not an action card.
     */
    private void handleActionCard(final Card card) throws Bell, Halt {
        switch (card.type()) {
        case BELL:
            throw new Bell("Card indicated bell", card);
        case HALT:
            LOG.debug("Received halt card.");
            throw new Halt("Card indicated halt", card);
        case PRINT:
            Optional<BigInteger> value = this.mill.mostRecentValue();
            if (!value.isPresent()) {
                LOG.error("No value is available for printing. Not printing.");
                break;
            }
            String printed = this.printer.print(value.get());
            LOG.debug("Attendant received value from printer: {}", printed);
            this.attendant.receiveOutput(printed);
            break;
        default:
            throw new IllegalArgumentException("Expected action card, not "
                    + card);
        }
    }

    /**
     * Instructs the attendant to perform the action specified by the card.
     * 
     * This includes annotating and formatting numbers printed to the final
     * report.
     * 
     * @param card
     *            An attendant action card.
     * @throws IllegalArgumentException
     *             if the card is not an attendant action card.
     */
    private void handleAttendantAction(final Card card) {
        switch (card.type()) {
        case ANNOTATE:
            String message = card.argument(0);
            LOG.debug("Annotating in final report: {}", message);
            this.attendant.annotate(message);
            break;
        case NEWLINE:
            LOG.debug("Writing a new line in the final report.");
            this.attendant.writeNewline();
            break;
        case WRITECOLUMNS:
            LOG.debug("Writing in rows.");
            this.attendant.writeInDirection(WriteDirection.COLUMNS);
            break;
        case WRITEROWS:
            LOG.debug("Writing in columns.");
            this.attendant.writeInDirection(WriteDirection.ROWS);
            break;
        case WRITEPICTURE:
            String format = card.argument(0);
            LOG.debug("Setting format string for numbers in final report: {}",
                    format);
            this.attendant.setFormat(format);
            break;
        default:
            throw new IllegalArgumentException(
                    "Expected attendant action card, not " + card);
        }

    }

    /**
     * Performs the specified action on the curve drawing device.
     * 
     * This includes setting the coordinates of the stylus and drawing with it.
     * 
     * @param card
     *            A curve drawing card.
     * @throws IllegalArgumentException
     *             if the card is not a curve drawing card.
     */
    private void handleCurveDrawing(final Card card) {
        Optional<BigInteger> value;
        switch (card.type()) {
        case DRAW:
            LOG.debug("Drawing on the curve printer");
            this.curvePrinter.draw();
            break;
        case MOVE:
            LOG.debug("Moving curve printer's stylus.");
            this.curvePrinter.move();
            break;
        case SETX:
            value = this.mill.mostRecentValue();
            if (!value.isPresent()) {
                LOG.error("No value is available for setting the x value of"
                          + " the curve printer; not setting a value.");
                break;
            }
            LOG.debug("Setting the x value of the curve printer: {}", value);
            this.curvePrinter.setX(value.get());
            break;
        case SETY:
            value = this.mill.mostRecentValue();
            if (!value.isPresent()) {
                LOG.error("No value is available for setting the y value of"
                          + " the curve printer; not setting a value.");
                break;
            }
            LOG.debug("Setting the y value of the curve printer: {}", value);
            this.curvePrinter.setY(value.get());
            break;
        default:
            throw new IllegalArgumentException(
                    "Expected curve drawing card, not " + card);
        }
    }

    /**
     * Handles Analytical Engine debugging cards, including comment and trace
     * cards.
     * 
     * @param card
     *            A debugging card.
     * @throws IllegalArgumentException
     *             if the specified card is not a debugging card.
     */
    private void handleDebuggingCard(final Card card) {
        switch (card.type()) {
        case COMMENT:
            LOG.debug("Comment: " + card.argument(0));
            break;
        case TRACEON:
            // TODO this should just change the logging level or something
            break;
        case TRACEOFF:
            // TODO this should just change the logging level or something
            break;
        default:
            throw new IllegalArgumentException("Expected debugging card, not "
                    + card);
        }

    }

    /**
     * Performs the memory access specified by the given card.
     * 
     * This includes reading from and writing to memory.
     * 
     * @param card
     *            A memory access card.
     * @throws BadCard
     *             if the card has a syntax error or induces a runtime error in
     *             the Analytical Engine.
     * @throws IllegalArgumentException
     *             if the card is not a memory access card.
     */
    private void handleMemoryAccess(final Card card) throws BadCard {
        int address;
        try {
            address = Integer.parseInt(card.argument(0));
        } catch (NumberFormatException e) {
            throw new BadCard("Illegal number format", card, e);
        }
        BigInteger value;
        switch (card.type()) {
        case LOAD:
            value = this.store.get(address);
            LOG.debug("Loading from {} into ingress axis: {}", address, value);
            this.mill.transferIn(value);
            break;
        case LOADPRIME:
            value = this.store.get(address);
            LOG.debug("Loading from {} into prime axis: {}", address, value);
            this.mill.transferIn(value, true);
            break;
        case NUMBER:
            value = new BigInteger(card.argument(1));
            LOG.debug("Loading number {} into address {}", value, address);
            this.store.put(address, value);
            break;
        case STORE:
            value = this.mill.transferOut();
            this.store.put(address, value);
            break;
        case STOREPRIME:
            value = this.mill.transferOut(true);
            this.store.put(address, value);
            break;
        case ZLOAD:
            value = this.store.get(address);
            this.store.put(address, BigInteger.ZERO);
            this.mill.transferIn(value);
            break;
        case ZLOADPRIME:
            value = this.store.get(address);
            this.store.put(address, BigInteger.ZERO);
            this.mill.transferIn(value, true);
            break;
        default:
            throw new IllegalArgumentException(
                    "Expected memory access card, not " + card);
        }

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void reset() {
        this.mill.reset();
        this.store.reset();
        this.curvePrinter.reset();
    }

    /**
     * {@inheritDoc}
     * 
     * @throws BadCard
     *             {@inheritDoc}
     */
    @Override
    public void run() throws BadCard {
        try {
            while (true) {
                Card currentCard = this.cardReader.readAndAdvance();
                try {
                    this.executeCard(currentCard);
                } catch (Bell bell) {
                    this.attendant.onBell(currentCard);
                }
            }
        } catch (BadCard e) {
            // LOG.error("Program error", e);
            throw e;
        } catch (Halt e) {
            // This would print the stack trace for the Halt exception.
            // LOG.info("Program halted.", e);
            LOG.info("Program halted.");
        } catch (IndexOutOfBoundsException e) {
            // LOG.error("Advance or reverse beyond boundary of card chain.",
            // e);
            throw e;
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @param attendant
     *            {@inheritDoc}
     */
    @Override
    public void setAttendant(final Attendant attendant) {
        this.attendant = attendant;
    }

    /**
     * {@inheritDoc}
     * 
     * @param reader
     *            {@inheritDoc}
     */
    @Override
    public void setCardReader(final CardReader reader) {
        this.cardReader = reader;
    }

    /**
     * {@inheritDoc}
     * 
     * @param printer
     *            {@inheritDoc}
     */
    @Override
    public void setCurvePrinter(final CurvePrinter printer) {
        this.curvePrinter = printer;
    }

    /**
     * {@inheritDoc}
     * 
     * @param mill
     *            {@inheritDoc}
     */
    @Override
    public void setMill(final Mill mill) {
        this.mill = mill;
    }

    /**
     * Sets the arithmetic operation to perform on the mill based on the
     * specified card.
     * 
     * {@code card} must have a type corresponding to one of the four
     * arithmetic operations. If not, an {@code IllegalArgumentException} will
     * be thrown.
     * 
     * @param card
     *            A card whose type corresponds to one of the four arithmetic
     *            operations.
     * @throws IllegalArgumentException
     *             if {@code type} does not correspond to one of the four
     *             arithmetic operations.
     */
    private void setOperation(final Card card) {
        Operation operation;
        String name;
        switch (card.type()) {
        case ADD:
            operation = Operation.ADD;
            name = "addition";
            break;
        case DIVIDE:
            operation = Operation.DIVIDE;
            name = "division";
            break;
        case MULTIPLY:
            operation = Operation.MULTIPLY;
            name = "multiplication";
            break;
        case SUBTRACT:
            operation = Operation.SUBTRACT;
            name = "subtraction";
            break;
        default:
            throw new IllegalArgumentException("Expected arithmetic card: "
                    + card);
        }
        LOG.debug("Setting operation on mill: {}", name);
        this.mill.setOperation(operation);
    }

    /**
     * {@inheritDoc}
     * 
     * @param printer
     *            {@inheritDoc}
     */
    @Override
    public void setPrinter(final Printer printer) {
        this.printer = printer;
    }

    /**
     * Performs the left or right shift specified by the given card.
     * 
     * @param card
     *            A left or right shift card.
     * @throws BadCard
     *             if there is a syntax error on the card.
     * @throws IllegalArgumentException
     *             if the card is not a shift card.
     */
    private void setShift(final Card card) throws BadCard {
        int shift;
        try {
            shift = Integer.parseInt(card.argument(0));
        } catch (NumberFormatException e) {
            throw new BadCard("Failed to parse step up value", card, e);
        }

        switch (card.type()) {
        case LSHIFTN:
            LOG.debug("Performing left shift on mill by {}", shift);
            try {
                this.mill.leftShift(shift);
            } catch (IllegalArgumentException e) {
                throw new BadCard("Shift value is out of bounds", card, e);
            }
            break;
        case RSHIFTN:
            LOG.debug("Performing right shift on mill by {}", shift);
            try {
                this.mill.rightShift(shift);
            } catch (IllegalArgumentException e) {
                throw new BadCard("Shift value is out of bounds", card, e);
            }
            break;
        default:
            throw new IllegalArgumentException("Expected shift card, not "
                    + card);
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @param store
     *            {@inheritDoc}
     */
    @Override
    public void setStore(final Store store) {
        this.store = store;
    }
} 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 class DefaultAttendant implements Attendant {

    /** Combinatorial card types that start a cycle. */
    private static final Collection<CardType> CYCLE_START_TYPES = Collections
            .unmodifiableCollection(
                    Arrays.asList(CardType.BACKSTART, CardType.CBACKSTART,
                            CardType.FORWARDSTART, CardType.CFORWARDSTART));

    /** Combinatorial card types that end a cycle. */
    private static final Collection<CardType> CYCLE_END_TYPES = Collections
            .unmodifiableCollection(Arrays.asList(CardType.BACKEND,
                    CardType.ALTERNATION, CardType.FORWARDEND));

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

    /**
     * Returns {@code true} if and only if the cycle started by card of type
     * {@code start} matches the cycle end specified by card of type {code
     * end}.
     * 
     * @param start
     *            A card type representing the start of a cycle.
     * @param end
     *            A card type representing the end of a cycle.
     * @return {@code true} if and only if the cycle started by card with type
     *         {@code start} matches the cycle end specified by type {code
     *         end}.
     */
    private static boolean cyclesMatch(final CardType start,
            final CardType end) {
        switch (start) {
        case BACKSTART:
        case CBACKSTART:
            // A ( or (? must be closed by a ).
            switch (end) {
            case BACKEND:
                return true;
            default:
                return false;
            }
        case FORWARDSTART:
        case CFORWARDSTART:
            // A { or {? must be closed by a } or }{
            switch (end) {
            case FORWARDEND:
            case ALTERNATION:
                return true;
            default:
                return false;
            }
        default:
            throw new IllegalArgumentException(
                    "Cannot check cycle matching for cards " + start + " and "
                            + end);
        }
    }

    /** The device in which the attendant loads the requested program. */
    private CardReader cardReader = null;

    /**
     * The format in which the attendant should write the output printed from
     * the Analytical Engine.
     */
    private String formatString = null;

    /** The library of built-in functions maintained by the attendant. */
    private Library library = null;

    /** The cumulative formatted output from the printer. */
    private String report = "";

    /**
     * Whether to remove comment cards from the program that will be loaded.
     */
    private boolean stripComments = false;

    /**
     * Whether to write the output from the Analytical Engine's printer in
     * rows.
     * 
     * If this is false, the output will be written in columns.
     */
    private boolean writeInRows = true;

    /**
     * {@inheritDoc}
     * 
     * @param message
     *            {@inheritDoc}
     */
    @Override
    public void annotate(final String message) {
        this.report += message;
        if (this.writeInRows) {
            this.report += System.lineSeparator();
        }
    }

    /**
     * Looks for cards that specify a global number of decimal places and
     * replaces any later cards that may be affected, like implicit shift or
     * number cards.
     * 
     * @param cards
     *            The list of cards to scan for decimal expansion.
     * @return A new list of cards representing the same program, but with the
     *         implicit decimals expanded into explicit decimals.
     * @throws BadCard
     *             if a card related to decimal expansion has invalid syntax.
     */
    private List<Card> expandDecimal(final List<Card> cards) throws BadCard {
        List<Card> result = new ArrayList<Card>();
        int decimalPlace = -1;
        for (Card card : cards) {
            // This variable shows up in more than one case, but we cannot
            // define it within each case because each case has the same
            // (switch-level) scope, even though it would be more readable that
            // way.
            Card replacement = null;
            switch (card.type()) {
            // A set decimal places to [+/-]n
            case DECIMALEXPAND:
                String dspec = card.argument(0);
                int relative = 0;

                if (dspec.charAt(0) == '+' || dspec.charAt(0) == '-') {
                    if (decimalPlace == -1) {
                        String msg = "I cannot accept a relative decimal place"
                                + "setting\nwithout a prior absolute setting.";
                        throw new BadCard(msg, card);
                    }
                    if (dspec.charAt(0) == '+') {
                        relative = 1;
                    } else {
                        relative = -1;
                    }
                    dspec = dspec.substring(1);
                }
                int d;
                try {
                    d = Integer.parseInt(dspec);
                } catch (NumberFormatException e) {
                    String msg = "I cannot find the number of decimal places"
                            + " you wish to use.";
                    throw new BadCard(msg, card, e);
                }
                if (relative != 0) {
                    d = decimalPlace + d * relative;
                }

                if (!this.stripComments) {
                    replacement = Card.commentCard(
                            "A set decimal places to " + card.argument(0));
                    result.add(replacement);
                }
                decimalPlace = d;
                break;
            // Convert "A write numbers with decimal point" to a picture
            case WRITEDECIMAL:
                if (decimalPlace < 0) {
                    String msg = "I cannot add the number of decimal places"
                            + " because\n you have not instructed me how many"
                            + " decimal\nplaces to use in a prior \"A set"
                            + " decimal places to\"\ninstruction.";
                    throw new BadCard(msg, card);
                }

                replacement = this.expandWriteDecimal(decimalPlace);
                result.add(replacement);
                break;
            /*
             * Replace number cards with decimal points with cards scaled to
             * the proper number of digits.
             */
            case NUMBER:
                String number = card.argument(1);
                int decimalIndex = number.indexOf('.');
                // If the number has no decimal, just add the card as is.
                if (decimalIndex < 0) {
                    result.add(card);
                    break;
                }
                if (decimalPlace < 0) {
                    String msg = "I cannot add the number of decimal places"
                            + " because\n you have not instructed me how many"
                            + " decimal\nplaces to use in a prior \"A set"
                            + " decimal places to\"\ninstruction.";
                    throw new BadCard(msg, card);
                }
                replacement = this.expandNumber(decimalPlace, card);
                result.add(replacement);
                break;
            // Add step up/down to "<" or ">" if not specified
            case LSHIFT:
            case RSHIFT:
                if (decimalPlace < 0) {
                    String msg = "I cannot add the number of decimal places"
                            + " because\n you have not instructed me how many"
                            + " decimal\nplaces to use in a prior \"A set"
                            + " decimal places to\"\ninstruction.";
                    throw new BadCard(msg, card);
                }

                replacement = this.expandShift(decimalPlace, card);
                result.add(replacement);
                break;
            default:
                // If no change needs to be made, just add the card as-is.
                result.add(card);
                break;
            }
        }
        return result;
    }

    /**
     * Returns a new number card that represents the same number, but trimmed
     * or expanded to have the specified number of digits after the decimal
     * point.
     * 
     * @param decimalPlace
     *            The number of digits the number should have after the decimal
     *            point.
     * @param card
     *            A card with type {@link CardType#NUMBER}.
     * @return A new card that represents the same number as on the original
     *         card, but with the specified number of digits after the decimal
     *         point.
     */
    private Card expandNumber(final int decimalPlace, final Card card) {
        // these two lines are repeated in the calling function, but we
        // repeat them here so that this method has only the two parameters
        String number = card.argument(1);
        int decimalIndex = number.indexOf('.');

        /*
         * Now adjust the decimal part to the given number of decimal places by
         * trimming excess digits and appending zeroes as required.
         */
        String dpart = number.substring(decimalIndex + 1);
        if (dpart.length() > decimalPlace) {
            /*
             * If we're trimming excess digits, round the remaining digits
             * based on the first digit of the portion trimmed.
             */
            if (Character.digit(dpart.charAt(decimalPlace), 10) >= 5) {
                dpart = new BigInteger(dpart.substring(0, decimalPlace), 10)
                        .add(BigInteger.ONE).toString();
            } else {
                dpart = dpart.substring(0, decimalPlace);
            }
        }

        StringBuilder afterDecimal = new StringBuilder(dpart);
        while (afterDecimal.length() < decimalPlace) {
            afterDecimal.append("0");
        }

        // Append the decimal part to fixed part from card
        String newNumber = number.substring(0, decimalIndex) + afterDecimal;
        if (this.stripComments) {
            return new Card(CardType.NUMBER,
                    new String[] { card.argument(0), newNumber });
        }
        return new Card(CardType.NUMBER,
                new String[] { card.argument(0), newNumber },
                "Decimal expansion by attendant");
    }

    /**
     * Replaces the given implicit left or right shift card with the
     * corresponding explicit shift card, with a shift of the specified number
     * of decimal places.
     * 
     * @param decimalPlace
     *            The number of digits by which to shift.
     * @param card
     *            An implicit left or right shift card.
     * @return An explicit left or right shift card with a shift argument of
     *         the specified number of decimal places.
     */
    private Card expandShift(final int decimalPlace, final Card card) {
        CardType newType = null;
        if (card.type() == CardType.LSHIFT) {
            newType = CardType.LSHIFTN;
        } else {
            newType = CardType.RSHIFTN;
        }

        String newArg = String.valueOf(decimalPlace);
        if (this.stripComments) {
            return new Card(newType, new String[] { newArg });
        }
        return new Card(newType, new String[] { newArg },
                "Step count added by attendant");
    }

    /**
     * Replaces a {@link CardType#WRITEDECIMAL} card with a
     * {@link CardType#WRITEPICTURE} card that instructs the attendant to
     * format the output with the specified number of decimal places.
     * 
     * @param decimalPlace
     *            The number of digits that the attendant should write after
     *            the decimal point.
     * @return A {@link CardType#WRITEPICTURE} card that instructs the
     *         attendant to format the output with the specified number of
     *         decimal places.
     */
    private Card expandWriteDecimal(final int decimalPlace) {
        int dpa;

        StringBuilder formatString = new StringBuilder("9.");
        for (dpa = 0; dpa < decimalPlace; dpa++) {
            formatString.append("9");
        }

        return new Card(CardType.WRITEPICTURE,
                new String[] { formatString.toString() });
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    @Override
    public String finalReport() {
        return this.report;
    }

    /**
     * Returns a formatted version of the specified number (given as a string)
     * according to the value specified previously by the
     * {@link #setFormat(String)} method.
     * 
     * @param input
     *            A number to format, given as a string.
     * @return The input number formatted according to the format specified by
     *         a previous invocation of {@link #setFormat(String)}.
     */
    private String formatted(final String input) {
        String s = input;
        boolean negative = input.charAt(0) == '-';
        boolean sign = false;

        if (this.formatString != null) {
            // s = v.abs().toString();
            if (negative) {
                s = input.substring(1);
            } else {
                s = input;
            }

            int i = this.formatString.length();
            String o = "";

            while (--i >= 0) {
                char c = this.formatString.charAt(i);

                switch (c) {
                case '9': // Digit, unconditionally
                    if (s.length() == 0) {
                        o = "0" + o;
                    } else {
                        o = s.substring(s.length() - 1, s.length()) + o;
                        s = s.substring(0, s.length() - 1);
                    }
                    break;

                case '#': // Digit, if number not exhausted
                    if (s.length() > 0) {
                        o = s.substring(s.length() - 1, s.length()) + o;
                        s = s.substring(0, s.length() - 1);
                    }
                    break;

                case ',': // Comma if digits remain to output
                    if (this.formatString.indexOf('9') >= 0
                            || s.length() > 0) {
                        o = c + o;
                    }
                    break;

                case '-': // Sign if negative
                    if (negative) {
                        o = '-' + o;
                        sign = true;
                    }
                    break;

                case '±': // Plus or minus sign
                    if (negative) {
                        o = '-' + o;
                    } else {
                        o = '+' + o;
                    }
                    sign = true;
                    break;

                case '+': // Sign if negative, space if positive
                    if (negative) {
                        o = '-' + o;
                    } else {
                        o = ' ' + o;
                    }
                    sign = true;
                    break;

                default: // Copy character to output
                    o = c + o;
                    break;
                }
            }
            /*
             * If there's any number "left over", write it to prevent
             * truncation without warning.
             */
            if (s.length() > 0) {
                o = s + o;
            }
            /*
             * If the number is negative and no sign has been output so far,
             * prefix it with a sign.
             */
            if (negative && !sign) {
                o = '-' + o;
            }
            s = o;
        }

        return s;
    }

    /**
     * {@inheritDoc}
     * 
     * @param cards
     *            {@inheritDoc}
     * @throws BadCard
     *             {@inheritDoc}
     * @throws IOException
     *             {@inheritDoc}
     * @throws UnknownCard
     *             {@inheritDoc}
     * @throws LibraryLookupException
     *             {@inheritDoc}
     */
    @Override
    public void loadProgram(final List<Card> cards)
            throws BadCard, IOException, UnknownCard, LibraryLookupException {
        LOG.debug("Examining cards before loading: " + cards);
        List<Card> result = cards;
        // Note: "A write numbers as ..." cards remain in the card chain.
        LOG.debug("Transcluding requested library functions");
        result = this.transcludeLibraryCards(result);
        if (this.stripComments) {
            LOG.debug("Remove comment cards");
            result.removeIf(c -> c.type().equals(CardType.COMMENT));
        }
        LOG.debug("Making requested decimal expansions");
        result = this.expandDecimal(result);
        // TODO this method should return a new list like the others do
        LOG.debug("Translating shorthand combinatoric cards");
        translateCombinatorics(result);
        LOG.debug("Mounting cards in reader: " + result);
        this.cardReader.mountCards(result);
    }

    /**
     * Log that a bell has been rung.
     * 
     * Subclasses that wish to perform some action on a bell ring should
     * override and reimplement this method.
     * 
     * @param card
     *            {@inheritDoc}
     */
    @Override
    public void onBell(Card card) {
        LOG.info("Bell! Caused by card: %s", card);
    }

    /**
     * {@inheritDoc}
     * 
     * @param printed
     *            {@inheritDoc}
     */
    @Override
    public void receiveOutput(final String printed) {
        this.report += this.formatted(printed);
        if (this.writeInRows) {
            this.report += System.lineSeparator();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void reset() {
        this.report = "";
        this.cardReader.unmountCards();
        this.library.clear();
    }

    /**
     * {@inheritDoc}
     * 
     * @param reader
     *            {@inheritDoc}
     */
    @Override
    public void setCardReader(final CardReader reader) {
        this.cardReader = reader;
    }

    /**
     * {@inheritDoc}
     * 
     * @param formatString
     *            {@inheritDoc}
     */
    @Override
    public void setFormat(final String formatString) {
        this.formatString = formatString;
    }

    /**
     * {@inheritDoc}
     * 
     * @param library
     *            {@inheritDoc}
     */
    @Override
    public void setLibrary(final Library library) {
        this.library = library;
    }

    /**
     * {@inheritDoc}
     * 
     * @param stripComments
     *            {@inheritDoc}
     * @see analyticalengine.attendant.Attendant#setStripComments(boolean)
     */
    @Override
    public void setStripComments(final boolean stripComments) {
        this.stripComments = stripComments;
    }

    /**
     * Scans the list of cards for any inclusion requests and replaces those
     * inclusion cards with the cards of the requested function.
     * 
     * @param cards
     *            The list of cards to scan for include requests.
     * @return A new list of cards representing the same program, but with the
     *         include requests replaced with the cards necessary to execute
     *         the requested function.
     * @throws BadCard
     *             if the requested file could not be found.
     * @throws IOException
     *             if there was a problem reading the file containing the
     *             requested function.
     * @throws UnknownCard
     *             if there was a syntax error in the file containing the
     *             requested function.
     * @throws LibraryLookupException
     *             if there was a problem reading the requested library
     *             function.
     */
    private List<Card> transcludeLibraryCards(final List<Card> cards)
            throws BadCard, IOException, UnknownCard, LibraryLookupException {

        List<Card> result = new ArrayList<Card>();
        for (Card card : cards) {
            /*
             * We recognise a card which begins with an "@" or the more
             * Victorian " A include cards " as an instruction to the attendant
             * to locate the chain of cards bearing that label and interpolate
             * it into the chain, replacing the card requesting it. This allows
             * commonly used sequences of calculation to be called out without
             * having to physically copy them into the original card chain.
             */
            if (card.type() == CardType.INCLUDE) {
                Path path = Paths.get(card.argument(0));
                if (!Files.exists(path)) {
                    throw new BadCard("Could not find file: " + path, card);
                }

                result.add(Card.commentCard(
                        "Begin interpolation of " + card + " by attendant"));
                for (String line : Files.readAllLines(path)) {
                    result.add(Card.fromString(line));
                }
                result.add(Card.commentCard(
                        "Endinterpolation of " + card + " by attendant"));
                /*
                 * The analyst can request the inclusion of a set of cards from
                 * the machine's library with the request to the attendant:
                 * 
                 * A include from library cards for <whatever>
                 * 
                 * The attendant searches for a set of cards named <whatever>
                 * and, if found, they are strung into the chain, replacing the
                 * library request.
                 */
            } else if (card.type() == CardType.INCLUDELIB) {
                String filename = card.argument(0);
                List<Card> newCards;
                newCards = this.library.find(filename);

                result.add(Card.commentCard("Begin interpolation of "
                        + filename + " from library by attendant"));
                result.addAll(newCards);
                result.add(Card.commentCard("End interpolation of " + filename
                        + " from library by attendant"));
            } else {
                result.add(card);
            }
        }

        return result;
    }

    /**
     * Scans the list of cards for implicit combinatorial (control) cards and
     * replaces them (in-place) with the corresponding explicit forward and
     * backward jump cards.
     * 
     * @param cards
     *            The list of cards to scan for implicit loops.
     * @throws BadCard
     *             if there is a syntax error in one of the implicit
     *             combinatorial cards (for example, if a start card has no
     *             corresponding end card).
     */
    private void translateCombinatorics(final List<Card> cards)
            throws BadCard {
        for (int i = 0; i < cards.size(); i++) {
            if (CYCLE_START_TYPES.contains(cards.get(i).type())) {
                LOG.debug("Translating cycle starting from " + cards.get(i)
                        + " at index " + i);
                this.translateCycle(cards, i);
            }
        }
    }

    /**
     * Replaces the implicit cycle (loop) starting at the specified index in
     * the given list of cards with explicit forward and/or backward jump
     * cards.
     * 
     * @param cards
     *            The list of cards containing the implicit loop to replace
     *            (in-place).
     * @param startIndex
     *            The index of the implicit loop to replace.
     * @throws BadCard
     *             if there is a syntax error in one of the implicit
     *             combinatorial cards (for example, if a start card has no
     *             corresponding end card).
     */
    private void translateCycle(final List<Card> cards, final int startIndex)
            throws BadCard {
        Card c = cards.get(startIndex);
        boolean depends = false;
        LOG.debug("Translating cycle starting at " + c);

        depends = c.type() == CardType.CBACKSTART
                || c.type() == CardType.CFORWARDSTART;
        int start = startIndex;
        if (this.stripComments) {
            cards.remove(start);
            start--;
        } else {
            cards.remove(start);
            cards.add(start, Card.commentCard(c + " Translated by attendant"));
        }

        /*
         * Search for the end of this cycle. If a sub-cycle is detected,
         * recurse to translate it.
         */

        for (int i = start + 1; i < cards.size(); i++) {
            Card u = cards.get(i);
            LOG.debug("looking at card " + u);
            if (CYCLE_START_TYPES.contains(u.type())) {
                LOG.debug("Making a recursive call from new start index " + i);
                translateCycle(cards, i);
            }
            if (CYCLE_END_TYPES.contains(u.type())) {
                LOG.debug("  Found end of cycle starting with " + c);

                if (!cyclesMatch(c.type(), u.type())) {
                    throw new BadCard("End of cycle does not match " + c
                            + " beginning on card " + start, u);
                }
                cards.remove(i);
                if (!this.stripComments) {
                    // cards.remove(i);
                    cards.add(i,
                            Card.commentCard(u + " Translated by attendant"));
                }
                if (c.type().equals(CardType.BACKSTART)
                        || c.type().equals(CardType.CBACKSTART)) {
                    // It's a loop
                    CardType newType;
                    if (depends) {
                        newType = CardType.CBACKWARD;
                    } else {
                        newType = CardType.BACKWARD;
                    }
                    String[] newArgs = { Integer.toString(i - start) };
                    cards.add(i, new Card(newType, newArgs));
                    LOG.debug("  (replaced with " + cards.get(i) + ")");
                    return;
                }

                // If we've reached this point, it's a forward skip, possibly
                // with an else clause
                CardType newType;
                if (depends) {
                    newType = CardType.CFORWARD;
                } else {
                    newType = CardType.FORWARD;
                }

                boolean isElse = u.type().equals(CardType.ALTERNATION);
                int argument;
                if (isElse) {
                    if (this.stripComments) {
                        argument = 0;
                    } else {
                        argument = 1;
                    }
                } else {
                    if (this.stripComments) {
                        argument = -1;
                    } else {
                        argument = 0;
                    }
                }
                argument += Math.abs(i - start);
                String[] newArgs = { Integer.toString(argument) };
                cards.add(start + 1, new Card(newType, newArgs));

                // Translate else branch of conditional, if present
                if (isElse) {
                    int j;

                    for (j = i + 1; j < cards.size(); j++) {
                        u = cards.get(j);
                        CardType uType = u.type();
                        if (CYCLE_START_TYPES.contains(uType)) {
                            translateCycle(cards, j);
                        }
                        if (CYCLE_END_TYPES.contains(uType)) {
                            if (!uType.equals(CardType.FORWARDEND)
                                    && !uType.equals(CardType.ALTERNATION)) {
                                throw new BadCard(
                                        "End of else cycle does not match " + c
                                                + " beginning on card " + i,
                                        u);
                            }
                            cards.remove(j);
                            if (!this.stripComments) {
                                cards.add(j, Card.commentCard(
                                        u + " Translated by attendant"));
                            }
                            CardType newType2 = CardType.FORWARD;
                            String[] newArgs2 = {
                                    Integer.toString(Math.abs(j - i) - 1) };
                            int location = i;
                            if (this.stripComments) {
                                location += 1;
                            } else {
                                location += 2;
                            }
                            cards.add(location, new Card(newType2, newArgs2));
                            return;
                        }
                    }
                }
                return;
            }
        }
        for (Card card : cards) {
            LOG.debug(card.toString());
        }
        throw new BadCard("No matching end of cycle.", c);
    }

    /**
     * {@inheritDoc}
     * 
     * @param direction
     *            {@inheritDoc}
     */
    @Override
    public void writeInDirection(final WriteDirection direction) {
        this.writeInRows = direction == WriteDirection.ROWS;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void writeNewline() {
        this.report += System.getProperty("line.separator");
    }

} public class DefaultLibrary implements Library {

    /**
     * A list of paths to search when a card requesting a library function is
     * encountered.
     */
    private List<Path> paths = new ArrayList<Path>();

    /**
     * {@inheritDoc}
     * 
     * @param path
     *            {@inheritDoc}
     */
    @Override
    public void addLibraryPath(final Path path) {
        this.paths.add(path);
    }

    /**
     * {@inheritDoc}
     * 
     * @param paths
     *            {@inheritDoc}
     */
    @Override
    public void addLibraryPaths(final List<Path> paths) {
        this.paths.addAll(paths);
    }

    /**
     * Returns the cards contained in the program at the specified URL.
     * 
     * @param fileurl
     *            The URL of the file containing the cards to return.
     * @return The cards contained in the program at the specified URL.
     * @throws URISyntaxException
     *             if the given URL has an unknown or invalid syntax.
     * @throws IOException
     *             if there is a problem reading the library file, or the JAR
     *             containing the library file.
     * @throws UnknownCard
     *             if the requested library file has a syntax error.
     * @throws IllegalArgumentException
     *             if the requested URL indicates a JAR file instead of a file
     *             within a JAR file.
     */
    private List<Card> cardsFromResource(final URL fileurl)
            throws URISyntaxException, IOException, UnknownCard {
        // Create some cards which will be placed at the beginning and end of
        // the card chain to indicate to the user that the intermediate cards
        // come from an included library file.
        Card initialComment = Card.commentCard("Begin interpolation of "
                + fileurl + " from library by attendant");
        Card terminalComment = Card.commentCard("End interpolation of "
                + fileurl + " from library by attendant");

        // Get the scheme and scheme-specific part of the given URL. Getting
        // the raw scheme-specific part (that is, without URL-encoded
        // characters) seems to be necessary due to limitations in Java.
        URI uri = fileurl.toURI();
        String scheme = uri.getScheme();
        String spec = uri.getRawSchemeSpecificPart();

        // If the scheme indicates that the file is inside a JAR file, as
        // might happen if this code is running from an executable JAR and the
        // requested library file is contained within the JAR, do some black
        // magic to open the JAR file and read the program from a file
        // contained within it.
        //
        // A jar:// scheme URL looks like
        //
        // jar:///path/to/jarfile!/path/within/jarfile
        //
        // The part before the "!/" is the path to the JAR file (which is just
        // a zipped directory). The part after the "!/" is the path to the
        // requested file relative to the root of the JAR file.
        List<Card> result = new ArrayList<Card>();
        if (scheme.equals("jar")) {
            int sep = spec.indexOf("!/");
            if (sep == -1) {
                throw new IllegalArgumentException(
                        "Cannot load a JAR file directly.");
            }
            URI zipUri = new URI(scheme, spec.substring(0, sep), null);
            Map<String, Object> env = Collections.<String, Object> emptyMap();
            try (FileSystem zipFs = FileSystems.newFileSystem(zipUri, env)) {
                Path programPath = Paths.get(uri);
                for (String line : Files.readAllLines(programPath)) {
                    result.add(Card.fromString(line));
                }
            }
        } else {
            // The scheme did not indicate a JAR file, so we get the path to
            // the file as usual, without any black magic.
            // Path programPath = Paths.get(new URI(scheme, spec, null));
            Path programPath = Paths.get(uri);
            for (String line : Files.readAllLines(programPath)) {
                result.add(Card.fromString(line));
            }
        }

        // Surround the cards from the resource with delimiting comment cards.
        result.add(0, initialComment);
        result.add(terminalComment);
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clear() {
        this.paths.clear();
    }

    /**
     * {@inheritDoc}
     * 
     * @param filename
     *            {@inheritDoc}
     * @return {@inheritDoc}
     * @throws LibraryLookupException
     *             {@inheritDoc}
     */
    public List<Card> find(final String filename)
            throws LibraryLookupException {
        // we assume library files have the .ae file extension
        String fileWithExt = filename;
        if (!fileWithExt.endsWith(".ae")) {
            fileWithExt += ".ae";
        }

        /*
         * Check if the file to load lives in the current JAR file, or the
         * current directory in which this code is being executed. If this
         * class lives in an executable JAR, the URL will be something like
         * 
         * jar:file:/path/to/analyticalengine.jar!/analyticalengine/filename.ae
         * 
         * If this class lives on a real filesystem, the URL will be something
         * like
         * 
         * file:/path/to/analyticalengine/filename.ae
         */
        URL fileurl = this.getClass().getClassLoader()
                .getResource("analyticalengine/" + fileWithExt);

        // If the file exists as a resource available to the class loader, then
        // load the cards from that location.
        if (fileurl != null) {
            try {
                return this.cardsFromResource(fileurl);
            } catch (URISyntaxException | IOException | UnknownCard e) {
                throw new LibraryLookupException("Failed to load library file",
                        e);
            }
        }

        // If the above resource did not exist, look for the file in the list
        // of known paths.
        Optional<Path> filePath = this.findInPath(fileWithExt);

        // Raise an exception if the requested library file could not be found
        // in any of the known paths.
        if (!filePath.isPresent()) {
            throw new LibraryLookupException(
                    "Could not find library file: " + fileWithExt);
        }

        // If the file was found somewhere in one of the library paths, load
        // the program directly from that file.
        Path path = filePath.get();
        List<Card> cards = new ArrayList<Card>();
        try {
            for (String line : Files.readAllLines(path)) {
                cards.add(Card.fromString(line));
            }
        } catch (IOException | UnknownCard e) {
            throw new LibraryLookupException("Failed to load library file", e);
        }
        return cards;
    }

    /**
     * Searches the known library directories for file with the specified name.
     * 
     * @param filename
     *            The basename of the file to search for.
     * @return The path to the first occurrence of a file with the specified
     *         name, or an empty {@link Optional} if no such file could be
     *         found.
     */
    private Optional<Path> findInPath(final String filename) {
        for (Path path : this.paths) {
            Path libraryFile = path.resolve(filename);
            if (Files.exists(libraryFile)) {
                return Optional.of(libraryFile);
            }
        }
        return Optional.empty();
    }
} public interface Library {

    /**
     * Adds the specified path to the list of paths to search when including
     * external functions.
     * 
     * Multiple invocations of this method append new paths to the end of the
     * list, so paths added first will be searched first.
     * 
     * @param path
     *            A path to search for included functions.
     */
    void addLibraryPath(Path path);

    /**
     * Adds each of the specified paths to the list of paths to search when
     * including external functions.
     * 
     * The paths will be searched in the order specified by the iterator.
     * Multiple invocations of this method append new paths to the end of the
     * list.
     * 
     * @param paths
     *            A list of paths to search for included functions.
     */
    void addLibraryPaths(List<Path> paths);

    /** Empties the known library search paths. */
    void clear();

    /**
     * Returns the list of cards stored in the library file with the specified
     * name.
     * 
     * If no file extension is specified, this method searches for the file
     * with name {@code filename + ".ae"}.
     * 
     * Searches the list of paths specified previously in the
     * {@link #addLibraryPath(Path)} and {@link #addLibraryPaths(List)} methods
     * for the specified file.
     * 
     * @param filename
     *            The file for which to search.
     * @return The list of cards stored in the specified library file.
     * @throws LibraryLookupException
     *             if the specified file is not found.
     */
    List<Card> find(String filename) throws LibraryLookupException;
} public class LibraryLookupException extends Exception {

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

    /**
     * Instantiates this exception with the specified error message.
     * 
     * @param message
     *            The error message.
     */
    public LibraryLookupException(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 LibraryLookupException(final String message,
            final Throwable cause) {
        super(message, cause);
    }

} 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 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 UnknownCard extends Exception {

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

    /**
     * Creates a new exception with the specified error message.
     * 
     * @param string
     *            The error message.
     */
    public UnknownCard(final String string) {
        super(string);
    }

} public class ArrayListCardReader implements CardReader {

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

    /**
     * The sequence of cards representing the instructions to the Engine.
     */
    private List<Card> cardChain = new ArrayList<Card>();

    /**
     * The index of the card before the next card to read in the card chain.
     */
    private int currentCard = -1;

    /**
     * {@inheritDoc}
     * 
     * @param n
     *            {@inheritDoc}
     * @throws IndexOutOfBoundsException
     *             {@inheritDoc}
     * @see analyticalengine.components.CardReader#advance(int)
     */
    @Override
    public void advance(final int n) {
        if (this.currentCard + n >= this.cardChain.size()) {
            throw new IndexOutOfBoundsException("No more cards to read.");
        }
        this.currentCard += n;
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * @see analyticalengine.components.CardReader#cards()
     */
    @Override
    public List<Card> cards() {
        return Collections.unmodifiableList(this.cardChain);
    }

    /**
     * {@inheritDoc}
     * 
     * @param cardChain
     *            {@inheritDoc}
     * @see analyticalengine.components.CardReader#mountCards(List)
     */
    @Override
    public void mountCards(final List<Card> cardChain) {
        LOG.debug("Mounting card chain {}", cardChain);
        this.cardChain = new ArrayList<Card>(cardChain);
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     * @throws Halt
     *             {@inheritDoc}
     * @see analyticalengine.components.CardReader#readAndAdvance()
     */
    @Override
    public Card readAndAdvance() throws Halt {
        if (this.currentCard + 1 == this.cardChain.size()) {
            throw new Halt("Reached end of card chain.");
        }
        this.currentCard += 1;
        return this.cardChain.get(this.currentCard);
    }

    /**
     * {@inheritDoc}
     * 
     * @param n
     *            {@inheritDoc}
     * @throws IndexOutOfBoundsException
     *             {@inheritDoc}
     * @see analyticalengine.components.CardReader#advance(int)
     */
    @Override
    public void reverse(final int n) {
        if (this.currentCard - n < -1) {
            throw new IndexOutOfBoundsException(
                    "Cannot reverse beyond beginning.");
        }
        this.currentCard -= n;
    }

    /**
     * {@inheritDoc}
     * 
     * @see analyticalengine.components.CardReader#unmountCards()
     */
    @Override
    public void unmountCards() {
        this.currentCard = -1;
        this.cardChain.clear();
    }

} 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 class DefaultMill implements Mill {

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

    /**
     * The width (number of digits) of an integer on which this mill can
     * operate.
     */
    public static final int WIDTH = 50;

    /**
     * One more than the maximum value of an integer that can be stored in
     * mill's axes.
     */
    private static final BigInteger MAXPLUSONE = BigInteger.TEN.pow(WIDTH);

    /** The maximum value of an integer that can be stored in mill's axes. */
    public static final BigInteger MAX = MAXPLUSONE.subtract(BigInteger.ONE);

    /** The minimum value of an integer that can be stored in mill's axes. */
    public static final BigInteger MIN = MAX.negate();

    /**
     * The index of the next axis to which a number will be loaded, either
     * directly or from the store.
     */
    private int currentAxis = 0;

    /** The operation to apply to the next two numbers loaded into the mill. */
    private Operation currentOperation = null;

    /**
     * The outgoing axes, which store the result of the arithmetic operations.
     * 
     * The first element of this array is the main egress axis. The last
     * element of this array is the prime egress axis, which represents the
     * high-order bits of the main egress axis, if any.
     * */
    private BigInteger[] egressAxes = new BigInteger[2];

    /**
     * The incoming axes, which store the operands of the arithmetic
     * operations.
     * 
     * The first two elements of this array are the main ingress axes. The last
     * element of this array is the prime ingress axis, which represents the
     * high-order bits of the first ingress axis (that is, the axis at index
     * 0), if any.
     */
    private BigInteger[] ingressAxes = new BigInteger[3];

    /**
     * The integers stored in the most recently used axis.
     * 
     * If a left shift or right shift is performed, this will be the value of
     * the first main ingress axis or the main egress axis, respectively. If a
     * load or store is performed, this will be the integer that is loaded or
     * stored. If an arithmetic operation is performed, this will be the value
     * of the main egress axis (after the operation is performed).
     */
    private BigInteger mostRecentValue = null;

    /**
     * Indicates either an overflow or a change of sign that occurred while
     * computing the result of an arithmetic operation.
     * 
     * It is impossible to know which of those options this flag indicates
     * without knowing the operation and the operands.
     */
    private boolean runUp;

    /**
     * Executes the arithmetic operation specified by the value of
     * {@link #currentOperation} and stores the result in the egress axes.
     * 
     * This method is invoked after two values (and possibly an additional
     * integer in the prime axis) have been loaded into the ingress axes.
     * 
     * This method sets the value of {@link #mostRecentValue} to be the result
     * of the arithmetic operation.
     * 
     * If there is an arithmetic overflow or a change of operation, the run up
     * flag will be set to {@code true}.
     */
    private void execute() {
        LOG.debug("Executing operation: " + this.currentOperation);
        LOG.debug("Ingress axes: " + Arrays.toString(this.ingressAxes));
        this.runUp = false;
        this.currentAxis = 0;
        BigInteger result = null;
        switch (this.currentOperation) {
        case ADD:
            result = this.ingressAxes[0].add(this.ingressAxes[1]);
            LOG.debug("Adding " + this.ingressAxes[0] + " to "
                    + this.ingressAxes[1]);
            /*
             * If the sum is greater than MAX, compensate for the overflow by
             * subtracting MAX from the sum (essentially computing the sum
             * modulo MAX) and setting the run up (overflow) flag.
             * 
             * If the first addend is nonnegative but the sum is negative, set
             * the run up (overflow) flag.
             * 
             * The run up lever is used to indicate both overflow and change of
             * sign, and it is not possible to distinguish which has happened
             * without knowing the addends.
             */
            if (result.compareTo(MAXPLUSONE) >= 0) {
                LOG.debug("Run up lever set: compensating for overflow.");
                this.runUp = true;
                result = result.subtract(MAXPLUSONE);
            } else if (this.ingressAxes[0].signum() >= 0
                    && result.signum() < 0) {
                LOG.debug("Run up lever set: change of sign.");
                this.runUp = true;
            }
            this.egressAxes[0] = result;
            this.egressAxes[1] = BigInteger.ZERO;
            LOG.debug("Sum: " + this.egressAxes[0]);
            break;
        case DIVIDE:
            BigInteger dividend = this.ingressAxes[0];

            // check for division by zero
            if (this.ingressAxes[1].signum() == 0) {
                LOG.warn("Division by zero detected.");
                this.egressAxes[0] = BigInteger.ZERO;
                this.egressAxes[1] = BigInteger.ZERO;
                this.runUp = true;
                break;
            }

            // add the high-order digits to the dividend
            if (this.ingressAxes[2].signum() != 0) {
                dividend = dividend.add(this.ingressAxes[2]
                        .multiply(MAXPLUSONE));
            }
            LOG.debug("Computed dividend: " + dividend);

            // compute the quotient and the remainder
            BigInteger[] quotientRemainder = dividend
                    .divideAndRemainder(this.ingressAxes[1]);

            // overflow if the quotient is more than maximum number of digits
            if (quotientRemainder[0].abs().compareTo(MAX) > 0) {
                LOG.debug("Overflow detected.");
                this.egressAxes[0] = BigInteger.ZERO;
                this.egressAxes[1] = BigInteger.ZERO;
                this.runUp = true;
                break;
            }

            this.egressAxes[0] = quotientRemainder[1];
            this.egressAxes[1] = quotientRemainder[0];
            LOG.debug("Remainder and quotient: "
                    + Arrays.toString(this.egressAxes));
            break;
        case MULTIPLY:
            result = this.ingressAxes[0].multiply(this.ingressAxes[1]);
            /*
             * Check for product longer than one column and set the primed
             * egress axis to the upper part.
             */
            if (result.abs().compareTo(MAX) > 0) {
                BigInteger[] qr = result.divideAndRemainder(MAXPLUSONE);
                this.egressAxes[0] = qr[1];
                this.egressAxes[1] = qr[0];
            } else {
                this.egressAxes[0] = result;
                this.egressAxes[1] = BigInteger.ZERO;
            }
            break;
        case SUBTRACT:
            result = this.ingressAxes[0].subtract(this.ingressAxes[1]);
            /*
             * Check for passage through negative infinity (borrow) and set run
             * up and trim value as a result.
             */
            if (result.compareTo(MIN) < 0) {
                this.runUp = true;
                result = result.add(MAXPLUSONE).negate();
            } else if (this.ingressAxes[0].signum() >= 0
                    && result.signum() < 0) {
                /*
                 * Run up gets set when the result of a subtraction changes the
                 * sign of the first argument. Note that since the same lever
                 * is used to indicate borrow and change of sign, it is not
                 * possible to distinguish overflow from sign change.
                 */
                this.runUp = true;
            }
            this.egressAxes[0] = result;
            this.egressAxes[1] = BigInteger.ZERO;
            break;
        default:
            break;
        }
        this.mostRecentValue = this.egressAxes[0];
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    @Override
    public boolean hasRunUp() {
        return this.runUp;
    }

    /**
     * {@inheritDoc}
     * 
     * @param shift
     *            {@inheritDoc}
     * @throws IllegalArgumentException
     *             {@inheritDoc}
     */
    @Override
    public void leftShift(final int shift) {
        LOG.debug("Performing left shift by {}", shift);
        if (shift < 0 || shift > 2 * WIDTH) {
            throw new IllegalArgumentException("Shift is out of bounds: "
                    + shift);
        }
        /*
         * A left shift is performed before a fixed point division, so the two
         * ingress axes containing the dividend are shifted.
         */
        BigInteger value = this.ingressAxes[0];
        if (this.ingressAxes[2].signum() != 0) {
            value = value.add(this.ingressAxes[2].multiply(MAXPLUSONE));
        }
        LOG.debug("Value to shift: {}", value);

        BigInteger sf = BigInteger.TEN.pow(shift);
        BigInteger pr = value.multiply(sf);

        LOG.debug("Shifted to: {}", pr);

        if (pr.compareTo(MAX) == 0) {
            this.ingressAxes[0] = pr;
            this.ingressAxes[2] = BigInteger.ZERO;
        } else {
            BigInteger[] pq = pr.divideAndRemainder(MAXPLUSONE);
            this.ingressAxes[0] = pq[1];
            this.ingressAxes[2] = pq[0];
        }
        LOG.debug("Set new ingress axes: {}",
                Arrays.toString(this.ingressAxes));
        this.mostRecentValue = this.ingressAxes[0];
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    @Override
    public Optional<BigInteger> mostRecentValue() {
        return Optional.ofNullable(this.mostRecentValue);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void reset() {
        this.currentOperation = null;
        this.currentAxis = 0;
        this.ingressAxes = new BigInteger[3];
        this.egressAxes = new BigInteger[2];
        this.runUp = false;
    }

    /**
     * {@inheritDoc}
     * 
     * @param shift
     *            {@inheritDoc}
     * @throws IllegalArgumentException
     *             {@inheritDoc}
     */
    @Override
    public void rightShift(final int shift) {
        LOG.debug("Performing right shift by {}", shift);
        if (shift < 0 || shift > 2 * WIDTH) {
            throw new IllegalArgumentException("Shift is out of bounds: "
                    + shift);
        }
        /*
         * A right shift is used to normalise after a fixed point
         * multiplication, so the egress axes are used.
         */
        BigInteger value = this.egressAxes[0];
        if (this.egressAxes[1].signum() != 0) {
            value = value.add(this.egressAxes[1].multiply(MAXPLUSONE));
        }

        BigInteger shiftFactor = BigInteger.TEN.pow(shift);
        BigInteger[] qr = value.divideAndRemainder(shiftFactor);

        if (qr[0].compareTo(MAX) == 0) {
            this.egressAxes[0] = qr[0];
            this.egressAxes[1] = BigInteger.ZERO;
        } else {
            qr = qr[0].divideAndRemainder(MAXPLUSONE);
            this.egressAxes[0] = qr[1];
            this.egressAxes[1] = qr[0];
        }

        this.mostRecentValue = this.egressAxes[0];
    }

    /**
     * {@inheritDoc}
     * 
     * @param operation
     *            {@inheritDoc}
     */
    @Override
    public void setOperation(final Operation operation) {
        this.currentOperation = operation;
        this.currentAxis = 0;
    }

    /**
     * {@inheritDoc}
     * 
     * @param value
     *            {@inheritDoc}
     * @throws IllegalArgumentException
     *             {@inheritDoc}
     */
    @Override
    public void transferIn(final BigInteger value) {
        this.transferIn(value, false);
    }

    /**
     * {@inheritDoc}
     * 
     * @param value
     *            {@inheritDoc}
     * @param prime
     *            {@inheritDoc}
     * @throws IllegalArgumentException
     *             {@inheritDoc}
     */
    @Override
    public void transferIn(final BigInteger value, final boolean prime) {
        if (value.compareTo(MAX) > 0) {
            throw new IllegalArgumentException("Value too large to load: "
                    + value);
        } else if (value.compareTo(MIN) < 0) {
            throw new IllegalArgumentException("Value too small to load: "
                    + value);
        }

        this.mostRecentValue = value;

        if (prime) {
            this.ingressAxes[2] = value;
            return;
        }

        this.ingressAxes[this.currentAxis] = value;
        // When first ingress axis set, clear prime axis
        if (this.currentAxis == 0) {
            this.ingressAxes[2] = BigInteger.ZERO;
        }
        // Rotate to next ingress axis
        this.currentAxis = (this.currentAxis + 1) % 2;
        // Once enough arguments have been transferred in, implicitly execute
        // the requested operation.
        if (this.currentAxis == 0) {
            this.execute();
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @return {@inheritDoc}
     */
    @Override
    public BigInteger transferOut() {
        return this.transferOut(false);
    }

    /**
     * {@inheritDoc}
     * 
     * @param prime
     *            {@inheritDoc}
     * @return {@inheritDoc}
     */
    @Override
    public BigInteger transferOut(final boolean prime) {
        BigInteger result = null;
        if (prime) {
            result = this.egressAxes[1];
        } else {
            result = this.egressAxes[0];
        }
        this.mostRecentValue = result;
        return result;
    }
} public class HashMapStore implements Store {

    /** The maximum number of cells in the store. */
    public static final int MAX_ADDRESS = 1000;

    /** The width (number of digits) of an integer that can be stored. */
    public static final int WIDTH = 50;

    /** The maximum value of an integer that can be stored. */
    public static final BigInteger MAX_VALUE = BigInteger.TEN.pow(WIDTH)
            .subtract(BigInteger.ONE);

    /** The minimum value of an integer that can be stored. */
    public static final BigInteger MIN_VALUE = MAX_VALUE.negate();

    /** The hash map that provides the addressable, random-access storage. */
    private final DefaultHashMap<Integer, BigInteger> rack = new DefaultHashMap<Integer, BigInteger>();

    /**
     * Instantiates this object and initializes the underlying hash map.
     */
    public HashMapStore() {
        this.rack.setValueGenerator(k -> BigInteger.ZERO);
    }

    /**
     * {@inheritDoc}
     * 
     * @param address
     *            {@inheritDoc}
     * @param value
     *            {@inheritDoc}
     * @throws IndexOutOfBoundsException
     *             {@inheritDoc}
     */
    @Override
    public void put(final int address, final BigInteger value) {
        if (address < 0 || address > MAX_ADDRESS) {
            throw new IndexOutOfBoundsException("Address " + address
                    + " must be between " + 0 + " and " + MAX_ADDRESS);
        }
        if (value.compareTo(MIN_VALUE) < 0 || value.compareTo(MAX_VALUE) > 0) {
            throw new IllegalArgumentException("Value " + value
                    + " must be between " + MIN_VALUE + " and " + MAX_VALUE);
        }
        this.rack.put(address, value);
    }

    /**
     * {@inheritDoc}
     * 
     * @param address
     *            {@inheritDoc}
     * @return {@inheritDoc}
     * @throws IndexOutOfBoundsException
     *             {@inheritDoc}
     */
    @Override
    public BigInteger get(final int address) {
        if (address < 0 || address > MAX_ADDRESS) {
            throw new IndexOutOfBoundsException("Bad address: " + address);
        }
        return this.rack.get(address);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void reset() {
        this.rack.clear();
    }

} public class NullCurvePrinter implements CurvePrinter {

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

    /**
     * Does nothing.
     * 
     * @see analyticalengine.components.CurvePrinter#draw()
     */
    @Override
    public void draw() {
        LOG.debug("ignoring draw command");
    }

    /**
     * Does nothing.
     * 
     * @see analyticalengine.components.CurvePrinter#move()
     */
    @Override
    public void move() {
        LOG.debug("ignoring move command");
    }

    /**
     * Does nothing.
     * 
     * @see analyticalengine.components.CurvePrinter#reset()
     */
    @Override
    public void reset() {
        LOG.debug("ignoring reset command");
    }

    /**
     * Does nothing.
     * 
     * @param x
     *            {@inheritDoc}
     * 
     * @see analyticalengine.components.CurvePrinter#setX(java.math.BigInteger)
     */
    @Override
    public void setX(final BigInteger x) {
        LOG.debug("ignoring setX command");
    }

    /**
     * Does nothing.
     * 
     * @param y
     *            {@inheritDoc}
     * 
     * @see analyticalengine.components.CurvePrinter#setY(java.math.BigInteger)
     */
    @Override
    public void setY(final BigInteger y) {
        LOG.debug("ignoring setY command");
    }

} public class StringPrinter implements Printer {

    /**
     * {@inheritDoc}
     * 
     * @param value
     *            {@inheritDoc}
     * @return {@inheritDoc}
     * @see analyticalengine.components.Printer#print(BigInteger)
     */
    @Override
    public String print(final BigInteger value) {
        return value.toString();
    }

} public class JFrameCurvePrinter extends JFrame implements CurvePrinter {

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

    /** The height of the canvas, in pixels. */
    public static final int HEIGHT = 400;

    /** The width of the canvas, in pixels. */
    public static final int WIDTH = 400;

    /**
     * The current polyline to which new points will be added on draw commands.
     */
    private Polyline currentPolyline;

    /** The canvas on which to plot the curve. */
    private Plotter plotter = new Plotter();

    /** The current x location of the drawing pen. */
    private BigInteger x = BigInteger.ZERO;

    /** The current y location of the drawing pen. */
    private BigInteger y = BigInteger.ZERO;

    /**
     * Instantiates this printer with a canvas on which to plot the curve.
     */
    public JFrameCurvePrinter() {
        super("Analytical Engine - Plotter");

        // Hide the frame when closed, but don't exit the program.
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Create the plotter and place it in the frame.
        this.plotter.setSize(WIDTH, HEIGHT);
        this.getContentPane().add(this.plotter, BorderLayout.CENTER);

        // Size the frame.
        this.pack();

        // The stylus starts at the origin.
        this.currentPolyline = new Polyline();
        this.currentPolyline.addPoint(BigInteger.ZERO, BigInteger.ZERO);
        this.plotter.addPolyline(this.currentPolyline);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void draw() {
        if (!this.isVisible()) {
            this.setVisible(true);
        }
        this.currentPolyline.addPoint(x, y);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void move() {
        // end the current polyline and create a new one
        this.currentPolyline = new Polyline();
        this.currentPolyline.addPoint(this.x, this.y);
        this.plotter.addPolyline(this.currentPolyline);
    }

    /**
     * {@inheritDoc}
     * 
     * @see analyticalengine.components.CurvePrinter#reset()
     */
    @Override
    public void reset() {
        this.x = BigInteger.ZERO;
        this.y = BigInteger.ZERO;
        this.currentPolyline = new Polyline();
        this.plotter.clear();

        // The plotter should have access to the current polyline, and the
        // current polyline should have the origin as its only point.
        this.currentPolyline.addPoint(this.x, this.y);
        this.plotter.addPolyline(this.currentPolyline);
    }

    /**
     * {@inheritDoc}
     * 
     * @param x
     *            {@inheritDoc}
     * @see analyticalengine.components.CurvePrinter#setX(java.math.BigInteger)
     */
    @Override
    public void setX(final BigInteger x) {
        this.x = x;
    }

    /**
     * {@inheritDoc}
     * 
     * @param y
     *            {@inheritDoc}
     * @see analyticalengine.components.CurvePrinter#setY(java.math.BigInteger)
     */
    @Override
    public void setY(final BigInteger y) {
        this.y = y;
    }
}
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.JCommander;
import analyticalengine.AnalyticalEngine;
import analyticalengine.DefaultAnalyticalEngine;
import analyticalengine.attendant.Attendant;
import analyticalengine.attendant.DefaultAttendant;
import analyticalengine.attendant.DefaultLibrary;
import analyticalengine.attendant.Library;
import analyticalengine.attendant.LibraryLookupException;
import analyticalengine.cards.BadCard;
import analyticalengine.cards.Card;
import analyticalengine.cards.UnknownCard;
import analyticalengine.components.ArrayListCardReader;
import analyticalengine.components.CardReader;
import analyticalengine.components.DefaultMill;
import analyticalengine.components.HashMapStore;
import analyticalengine.components.NullCurvePrinter;
import analyticalengine.components.StringPrinter;
import analyticalengine.gui.JFrameCurvePrinter;
/**
 * Main.java - driver program that interprets and runs engine programs
 * 
 * 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.main;





/**
 * A command-line driver for the Analytical Engine simulation.
 * 
 * @author Jeffrey Finkelstein &lt;jeffrey.finkelstein@gmail.com&gt;
 * @since 0.0.1
 */
public final class Main {

    /** Instantiation disallowed. */
    private Main() {
        // intentionally unimplemented
    }

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

    /**
     * The main method for the command-line program.
     * 
     * @param argv
     *            The command-line arguments to the program.
     */
    public static void main(final String[] argv) {
        Arguments arguments = new Arguments();
        JCommander argparser = new JCommander(arguments, argv);

        if (arguments.help() || arguments.args().size() != 1) {
            argparser.usage();
            return;
        }

        if (arguments.verbosity() == 1) {
            // TODO increase debugging level to info
            LOG.debug("Requested verbosity 1; not yet implemented.");
        } else if (arguments.verbosity() == 2) {
            // TODO increase debugging level to debug
            LOG.debug("Requested verbosity 2; not yet implemented.");
        }

        // Create and hook up the components of the engine.
        //
        // The engine has a mill (ALU), a store (memory), a card reader
        // (instructions), a printer, a curve printer (plotter), and an
        // attendant (operator).
        //
        // The attendant has access to a library of built-in functions, and
        // access to the card reader used by the engine.
        Attendant attendant = new DefaultAttendant();
        CardReader cardReader = new ArrayListCardReader();
        attendant.setCardReader(cardReader);
        Library library = new DefaultLibrary();
        attendant.setLibrary(library);

        // apply any attendant-specific configuration from command-line args
        library.addLibraryPaths(arguments.libraryPath());
        // always search the current directory as well
        library.addLibraryPath(Paths.get("."));
        attendant.setStripComments(arguments.stripComments());

        AnalyticalEngine engine = new DefaultAnalyticalEngine();
        engine.setAttendant(attendant);
        engine.setCardReader(cardReader);
        engine.setMill(new DefaultMill());