/* * JFLAP - Formal Languages and Automata Package * * * Susan H. Rodger * Computer Science Department * Duke University * August 27, 2009 * Copyright (c) 2002-2009 * All rights reserved. * JFLAP is open source software. Please see the LICENSE for terms. * */ package automata.turing; import java.io.Serializable; /** * A tape for a Turing machine. The tape head can move across the tape, reading * and writing individual characters. * * @author Ryan Cavalcante */ public class Tape implements Serializable { /** * Instantiates an empty tape object. */ public Tape() { this(""); } /** * Instantiates a tape object with the tape head pointing to the first * character of input. * * @param input * the input string to write to the tape */ public Tape(String input) { buffer = new StringBuffer(); if (input.equals("")) input = "" + BLANK; buffer.insert(0, input); tapeHead = 0; } /** * Instantiates a tape that is a copy of a given tape. * * @param tape * the tape to copy */ public Tape(Tape tape) { this.buffer = new StringBuffer(tape.buffer.toString()); tapeHead = tape.getTapeHead(); cachedHash = tape.cachedHash; } /** * Writes character to the tape. * * @param character * the character to write to the tape. */ public void writeChar(char character) { buffer.deleteCharAt(tapeHead); buffer.insert(tapeHead, character); cachedHash = 0xdeadbeef; } /** * Writes symbol to the tape. * * @param symbol * the symbol to write to the tape. */ public void write(String symbol) { buffer.deleteCharAt(tapeHead); buffer.insert(tapeHead, symbol); cachedHash = 0xdeadbeef; } /** * Returns the character pointed to by the tape head. * * @return the character pointed to by the tape head. */ public char readChar() { char[] toReturn = new char[1]; buffer.getChars(tapeHead, tapeHead + 1, toReturn, 0); return toReturn[0]; } /** * Returns the character pointed to by the tape head in a string. * * @return a string representation of the character pointed to by the tape * head. */ public String read() { char[] toReturn = new char[1]; buffer.getChars(tapeHead, tapeHead + 1, toReturn, 0); return new String(toReturn); } /** * Moves the tape head in direction. * * @param direction * the direction to move the tape head. * @throws IllegalArgumentException * if direction is not one of "L", "R", or "S" */ public void moveHead(String direction) { try { switch (direction.charAt(0)) { case 'L': tapeHead--; break; case 'R': tapeHead++; break; case 'S': break; default: throw new IllegalArgumentException("Bad tape direction " + direction); } } catch (IndexOutOfBoundsException e) { throw new IllegalArgumentException( "Tape direction is empty string!"); } /** * If the tape head is moved to an index out of the range of the buffer, * the buffer needs to grow accordingly, filled with blanks where * appropriate. */ if (tapeHead >= buffer.length()) { int bufferLength = buffer.length(); buffer.setLength(tapeHead + 1); for (int k = bufferLength; k < buffer.length(); k++) { buffer.deleteCharAt(k); buffer.insert(k, BLANK); } } else if (tapeHead < 0) { int numToInsert = Math.abs(tapeHead); char[] toInsert = new char[numToInsert]; for (int i = 0; i < numToInsert; i++) { toInsert[i] = BLANK; } buffer.insert(0, toInsert); tapeHead = 0; } } /** * Returns the contents of the tape, from tape index 0 till the end of the * tape. * * @return the contents of the tape as a string */ public String getContents() { return buffer.toString(); } /** * Returns the output of the tape. The first character of the output * consists of the symbol underneath the tape head, and further consists of * all symbols to the right of the tape head in order until a blank is * encountered. In the event that the tape head is on a blank the empty * string is returned. * * @return the output of the tape */ public String getOutput() { int nextBlank = buffer.indexOf("" + BLANK, getTapeHead()); if (nextBlank == -1) nextBlank = buffer.length(); return buffer.substring(getTapeHead(), nextBlank); } /** * Returns the index in the buffer that the tape head is currently pointing * to. * * @return the index in the buffer that the tape head is currently pointing * to. */ public int getTapeHead() { return tapeHead; } /** * Returns a string representation of the tape object. * * @return a string representation of the tape object. */ public String toString() { return "[" + buffer.toString() + "]" + " TAPE HEAD AT " + tapeHead; } /** * Retrieves the "non-trivial" section of the tape. What is meant by * non-trivial is the section of tape modulo a prefix and suffix of blank * tape symbols. * * @param section * an array of two intergers, which will hold, when finished, the * index of the first non-blank character in the first entry, and * the index of the first blank character of the suffix. Here, * section[1]-section[0] is the length of the * non-trivial section. */ private void nonTrivial(int[] section) { int s, e; for (e = buffer.length() - 1; e > 0 && buffer.charAt(e) == BLANK; e--) ; if (buffer.charAt(e) != BLANK) e++; for (s = 0; s < e && buffer.charAt(s) == BLANK; s++) ; section[0] = s; section[1] = e; } /** * Compares two tapes for equality. Two tapes are equal if they contain the * same characters and are at the same position in the tape, modulo a prefix * of some blank characters. * * @param tape * the tape to compare against for equality * @return true if the tapes are equal, false * if they are not */ public boolean equals(Object tape) { if (tape == this) return true; Tape t; try { t = (Tape) tape; } catch (ClassCastException e) { return false; } // These variables are necessary for going into the tape so we // can consider everything other than the "blank" prefix. int[] first = new int[2], second = new int[2]; // Do not consider the blank prefixes. this.nonTrivial(first); t.nonTrivial(second); // If they're not the same length, who cares? if (first[1] - first[0] != second[1] - second[0]) return false; // If they're at different positions, who cares? if (tapeHead - first[0] != t.tapeHead - second[0]) return false; // If all else fails, compare the characters. for (; first[0] < first[1]; first[0]++, second[0]++) if (buffer.charAt(first[0]) != t.buffer.charAt(second[0])) return false; // We've made it! return true; } /** * Returns a hash code for this tape. * * @return a hash code for this tape */ public int hashCode() { if (cachedHash != 0xdeadbeef) return cachedHash; int[] bounds = new int[2]; this.nonTrivial(bounds); return cachedHash = buffer.substring(bounds[0], bounds[1]).hashCode(); } /** The string buffer. */ private StringBuffer buffer = new StringBuffer(); /** The tape head (index in buffer). */ private int tapeHead; /** The cached hash code, since it takes a bit to compute. */ private int cachedHash = 0xdeadbeef; /** The blank tape symbol. */ public static final char BLANK = '\u25A1'; }