/* * TDDD86 Huffman Encoding * This file contains the implementation of ibitstream and obitstream classes. * These classes are patterned after (and, in fact, inherit from) the standard * ifstream and ofstream classes. Please see bitstream.h for information about * how a client properly uses these classes. * * Please do not modify this provided file. Your turned-in files should work * with an unmodified version of all provided code files. */ #include #include "bitstream.h" #include "error.h" #include "strlib.h" static const int NUM_BITS_IN_BYTE = 8; inline int GetNthBit(int n, int fromByte) { return ((fromByte & (1 << n)) != 0); } inline void SetNthBit(int n, int & inByte) { inByte |= (1 << n); } /* * Returns a printable string for the given character. */ static string toPrintable(int ch) { if (ch == '\n') { return "'\\n'"; } else if (ch == '\t') { return "'\\t'"; } else if (ch == '\r') { return "'\\r'"; } else if (ch == '\f') { return "'\\f'"; } else if (ch == '\b') { return "'\\b'"; } else if (ch == '\0') { return "'\\0'"; } else if (ch == ' ') { return "' '"; } else if (ch == (int) PSEUDO_EOF) { return "EOF"; } else if (ch == (int) NOT_A_CHAR) { return "NONE"; } else if (!isgraph(ch)) { return "???"; } else { return string("'") + (char) ch + string("'"); } } /* Constructor ibitstream::ibitstream * ------------------------------ * Each ibitstream tracks 3 integers as private data. * "lastTell" is streampos of the last byte that was read (this is used * to detect when other non-readBit activity has changed the tell) * "curByte" contains contents of byte currently being read * "pos" is the bit position within curByte that is next to read * We set initial state for lastTell and curByte to 0, then pos is * set at 8 so that next readBit will trigger a fresh read. */ ibitstream::ibitstream() : istream(nullptr), lastTell(0), curByte(0), pos(NUM_BITS_IN_BYTE) {} /* Member function ibitstream::readBit * --------------------------------- * If bits remain in curByte, retrieve next and increment pos * Else if end of curByte (or some other read happened), then read next byte * and start reading from bit position 0 of that byte. * If read byte from file at EOF, return EOF. */ int ibitstream::readBit() { if (!is_open()) { error("Cannot read a bit from a stream that is not open."); } // if just finished bits from curByte or if data read from stream after last readBit() if (lastTell != tellg() || pos == NUM_BITS_IN_BYTE) { if ((curByte = get()) == EOF) { // read next single byte from file return EOF; } pos = 0; // start reading from first bit of new byte lastTell = tellg(); } int result = GetNthBit(pos, curByte); pos++; // advance bit position for next call to readBit return result; } /* Member function ibitstream::rewind * --------------------------------- * Simply seeks back to beginning of file, so reading begins again * from start. */ void ibitstream::rewind() { if (!is_open()) { error("Cannot rewind stream that is not open."); } clear(); seekg(0, ios::beg); } /* Member function ibitstream::size * ------------------------------ * Seek to file end and use tell to retrieve position. * In order to not disrupt reading, we also record cur streampos and * re-seek to there before returning. */ long ibitstream::size() { if (!is_open()) { error("Cannot get size of stream which is not open."); } clear(); // clear any error state streampos cur = tellg(); // save current streampos seekg(0, ios::end); // seek to end streampos end = tellg(); // get offset seekg(cur); // seek back to original pos return long(end); } /* Member function ibitstream::is_open * ------------------------------------------- * Default implementation of is_open has the stream always * open. Subclasses can customize this if they'd like. */ bool ibitstream::is_open() { return true; } /* Constructor obitstream::obitstream * ---------------------------------- * Each obitstream tracks 3 integers as private data. * "lastTell" is streampos of the last byte that was written (this is used * to detect when other non-writeBit activity has changed the tell) * "curByte" contains contents of byte currently being written * "pos" is the bit position within curByte that is next to write * We set initial state for lastTell and curByte to 0, then pos is * set at 8 so that next writeBit will start a new byte. */ obitstream::obitstream() : ostream(nullptr), lastTell(0), curByte(0), pos(NUM_BITS_IN_BYTE) {} /* Member function obitstream::writeBit * ---------------------------------- * If bits remain to be written in curByte, add bit into byte and increment pos * Else if end of curByte (or some other write happened), then start a fresh * byte at position 0. * We write the byte out for each bit (backing up to overwrite as needed), rather * than waiting for 8 bits. This is because the client might make * 3 writeBit calls and then start using << so we can't wait til full-byte * boundary to flush any partial-byte bits. */ void obitstream::writeBit(int bit) { if (bit != 0 && bit != 1) { error(string("writeBit must be passed an integer argument of 0 or 1. You passed the integer ") + toPrintable(bit) + " (" + integerToString(bit) + ")."); } if (!is_open()) { error("Cannot writeBit to stream which is not open."); } // if just filled curByte or if data written to stream after last writeBit() if (lastTell != tellp() || pos == NUM_BITS_IN_BYTE) { curByte = 0; // zero out byte for next writes pos = 0; // start writing to first bit of new byte } if (bit) { // only need to change if bit needs to be 1 (byte starts already zeroed) SetNthBit(pos, curByte); } if (pos == 0 || bit) { // only write if first bit in byte or changing 0 to 1 if (pos != 0) { seekp(-1, ios::cur); // back up to overwite if pos > 0 } put(curByte); } pos++; // advance to next bit position for next write lastTell = tellp(); } /* Member function obitstream::size * ------------------------------ * Seek to file end and use tell to retrieve position. * In order to not disrupt writing, we also record cur streampos and * re-seek to there before returning. */ long obitstream::size() { if (!is_open()) { error("Cannot get size of stream which is not open."); } clear(); // clear any error state streampos cur = tellp(); // save current streampos seekp(0, ios::end); // seek to end streampos end = tellp(); // get offset seekp(cur); // seek back to original pos return long(end); } /* Member function obitstream::is_open * ------------------------------------------- * Default implementation of is_open has the stream always * open. Subclasses can customize this if they'd like. */ bool obitstream::is_open() { return true; } /* Constructor ifbitstream::ifbitstream * ------------------------------------------- * Wires up the stream class so that it knows to read data * from disk. */ ifbitstream::ifbitstream() { init(&fb); } /* Constructor ifbitstream::ifbitstream * ------------------------------------------- * Wires up the stream class so that it knows to read data * from disk, then opens the given file. */ ifbitstream::ifbitstream(const char* filename) { init(&fb); open(filename); } ifbitstream::ifbitstream(string filename) { init(&fb); open(filename); } /* Member function ifbitstream::open * ------------------------------------------- * Attempts to open the specified file, failing if unable * to do so. */ void ifbitstream::open(const char* filename) { if (!fb.open(filename, ios::in | ios::binary)) { setstate(ios::failbit); } } void ifbitstream::open(string filename) { open(filename.c_str()); } /* Member function ifbitstream::is_open * ------------------------------------------- * Determines whether the file stream is open. */ bool ifbitstream::is_open() { return fb.is_open(); } /* Member function ifbitstream::close * ------------------------------------------- * Closes the file stream, if one is open. */ void ifbitstream::close() { if (!fb.close()) { setstate(ios::failbit); } } /* Constructor ofbitstream::ofbitstream * ------------------------------------------- * Wires up the stream class so that it knows to write data * to disk. */ ofbitstream::ofbitstream() { init(&fb); } /* Constructor ofbitstream::ofbitstream * ------------------------------------------- * Wires up the stream class so that it knows to write data * to disk, then opens the given file. */ ofbitstream::ofbitstream(const char* filename) { init(&fb); open(filename); } ofbitstream::ofbitstream(string filename) { init(&fb); open(filename); } /* Member function ofbitstream::open * ------------------------------------------- * Attempts to open the specified file, failing if unable * to do so. */ void ofbitstream::open(const char* filename) { /* Confirm we aren't about to do something that could potentially be a * Very Bad Idea. */ if (endsWith(filename, ".cpp") || endsWith(filename, ".h") || endsWith(filename, ".hh") || endsWith(filename, ".cc")) { error(string("It is potentially dangerous to write to file ") + filename + ", because that might be your own source code. " + "We are explicitly disallowing this operation. Please choose a " + "different filename."); setstate(ios::failbit); } else { if (!fb.open(filename, ios::out | ios::binary)) { setstate(ios::failbit); } } } void ofbitstream::open(string filename) { open(filename.c_str()); } /* Member function ofbitstream::is_open * ------------------------------------------- * Determines whether the file stream is open. */ bool ofbitstream::is_open() { return fb.is_open(); } /* Member function ofbitstream::close * ------------------------------------------- * Closes the given file. */ void ofbitstream::close() { if (!fb.close()) { setstate(ios::failbit); } } /* Constructor istringbitstream::istringbitstream * ------------------------------------------- * Sets the stream to use the string buffer, then sets * the initial string to the specified value. */ istringbitstream::istringbitstream(string s) { init(&sb); sb.str(s); } /* Member function istringbitstream::str * ------------------------------------------- * Sets the underlying string in the buffer to the * specified string. */ void istringbitstream::str(string s) { sb.str(s); } /* Member function ostringbitstream::ostringbitstream * ------------------------------------------- * Sets the stream to use the string buffer. */ ostringbitstream::ostringbitstream() { init(&sb); } /* Member function ostringbitstream::str * ------------------------------------------- * Retrives the underlying string data. */ string ostringbitstream::str() { return sb.str(); }