kolibrios/programs/develop/objconv/containers.h

394 lines
19 KiB
C
Raw Normal View History

/**************************** containers.h ********************************
* Author: Agner Fog
* Date created: 2006-07-15
* Last modified: 2007-02-01
* Project: objconv
* Module: containers.h
* Description:
* Header file for container classes and dynamic memory allocation
*
* Copyright 2006-2008 GNU General Public License http://www.gnu.org/licenses
*****************************************************************************/
/*****************************************************************************
This header file declares various container classes for dynamic allocation
of memory for files and other types of data with unpredictable sizes.
These classes have private access to the memory buffer in order to prevent
memory leaks. It is important to use these classes for all dynamic memory
allocation.
The class CMemoryBuffer and its descendants are used for many purposes of
storage of data with a size that is not known in advance. CMemoryBuffer
allows the size of its data to grow when new data are appended with the
Push() member function.
The class CFileBuffer, which is derived from CMemoryBuffer, is used for
reading, writing and storing object files and other files.
There are many different classes for different things you can do with
an object file. These classes, declared in converters.h, are all
descendants of CFileBuffer. It is possible to transfer a data buffer
from one object to another by the operator
A >> B
where A and B are both objects of classes that descend from CFileBuffer.
The file buffer that was owned by A is transferred to B and A is left empty
after the A >> B operation. This makes sure that a memory buffer is always
owned by one, and only one, object. The opposite operator B << A does the
same thing.
The >> operator is used whenever we want to do something to a file buffer
that requires a specialized class. The file buffer is transferred from the
object that owns it to an object of the specialized class and transferred
back again to the original owner when the object of the specialized class
has done its job.
You may say that the descendants of CFileBuffer have a chameleonic nature:
You can change the nature of a piece of data owned by an object by
transferring it to an object of a different class. This couldn't be done
by traditional polymorphism because it is not possible to change the class
of an object after it is created, and there are too many different things
you can do with object files for a single class to handle them all.
The container class CMemoryBuffer is useful for storing data of mixed types.
Data of arbitrary type can be accessed by Get<type>(offset) or by
Buf() + offset.
If all items in a dynamic array are of the same type then it is easier to
use one of the template classes CArrayBuf<> or CSList<>. These can be
used in the same way as normal arrays with the operator [].
CArrayBuf<> and CSList<> both have a member function SetNum() to allocate
the size. The size of CArrayBuf<> can be set only once, while the size of
CSList<> can be changed at any time. CSList<> also has a member function
Push() that adds records sequentially. CSList can be sorted if operators
< and == are defined for the record type.
Warning:
It is necessary to use CArrayBuf<> rather than CSList<> if the record type
has a constructor or destructor.
Warning:
It is not safe to make pointers to data inside a dynamic array of type
CMemoryBuffer or CSList<> because the buffer may be re-allocated when the
size grows. Such pointers will only work if we are finished with all push
operations. It is safer to address data inside the buffer by their index
or offset relative to the buffer.
*****************************************************************************/
#ifndef CONTAINERS_H
#define CONTAINERS_H
extern CErrorReporter err; // Defined in error.cpp
class CFileBuffer; // Declared below
void operator >> (CFileBuffer & a, CFileBuffer & b); // Transfer ownership of buffer and other properties
// Class CMemoryBuffer makes a dynamic array which can grow as new data are
// added. Used for storage of files, file sections, tables, etc.
class CMemoryBuffer {
public:
CMemoryBuffer(); // Constructor
~CMemoryBuffer(); // Destructor
void SetSize(uint32 size); // Allocate buffer of specified size
void SetDataSize(uint32 size); // Claim space as a data
uint32 GetDataSize() {return DataSize;}; // File data size
uint32 GetBufferSize(){return BufferSize;}; // Buffer size
uint32 GetNumEntries(){return NumEntries;}; // Get number of entries
uint32 Push(void const * obj, uint32 size); // Add object to buffer, return offset
uint32 PushString(char const * s); // Add ASCIIZ string to buffer, return offset
uint32 GetLastIndex(); // Index of last object pushed (zero-based)
void Align(uint32 a); // Align next entry to address divisible by a
int8 * Buf() {return buffer;}; // Access to buffer
template <class TX> TX & Get(uint32 Offset) { // Get object of arbitrary type from buffer
if (Offset >= DataSize) {err.submit(2016); Offset = 0;} // Offset out of range
return *(TX*)(buffer + Offset);}
private:
CMemoryBuffer(CMemoryBuffer&); // Make private copy constructor to prevent copying
int8 * buffer; // Buffer containing binary data. To be modified only by SetSize and operator >>
uint32 BufferSize; // Size of allocated buffer ( > DataSize)
protected:
uint32 NumEntries; // Number of objects pushed
uint32 DataSize; // Size of data, offset to vacant space
friend void operator >> (CFileBuffer & a, CFileBuffer & b); // Transfer ownership of buffer and other properties
};
static inline void operator << (CFileBuffer & b, CFileBuffer & a) {a >> b;} // Same as operator << above
// Class CFileBuffer is used for storage of input and output files
class CFileBuffer : public CMemoryBuffer {
public:
CFileBuffer(); // Default constructor
CFileBuffer(char const * filename); // Constructor
void Read(int IgnoreError = 0); // Read file into buffer
void Write(); // Write buffer to file
int GetFileType(); // Get file format type
void SetFileType(int type); // Set file format type
void Reset(); // Set all members to zero
static char const * GetFileFormatName(int FileType); // Get name of file format type
char const * FileName; // Name of input file
char const * OutputFileName; // Output file name
int WordSize; // Segment word size (16, 32, 64)
int FileType; // Object file type
int Executable; // File is executable
char * SetFileNameExtension(const char * f); // Set file name extension according to FileType
protected:
void GetOMFWordSize(); // Determine word size for OMF file
void CheckOutputFileName(); // Make output file name or check that requested name is valid
};
// Class CTextFileBuffer is used for building text files
class CTextFileBuffer : public CFileBuffer {
public:
CTextFileBuffer(); // Constructor
void Put(const char * text); // Write text string to buffer
void Put(const char character); // Write single character to buffer
void NewLine(); // Add linefeed
void Tabulate(uint32 i); // Insert spaces until column i
int LineType; // 0 = DOS/Windows linefeeds, 1 = UNIX linefeeds
void PutDecimal(int32 x, int IsSigned = 0); // Write decimal number to buffer
void PutHex(uint8 x, int MasmForm = 0); // Write hexadecimal number to buffer
void PutHex(uint16 x, int MasmForm = 0); // Write hexadecimal number to buffer
void PutHex(uint32 x, int MasmForm = 0); // Write hexadecimal number to buffer
void PutHex(uint64 x, int MasmForm = 0); // Write hexadecimal number to buffer
void PutFloat(float x); // Write floating point number to buffer
void PutFloat(double x); // Write floating point number to buffer
uint32 GetColumn() {return column;} // Get column number
protected:
uint32 column; // Current column
private:
uint32 PushString(char const * s){return 0;}; // Make PushString private to prevent using it
};
// Class CArrayBuf<RecordType> is used for dynamic arrays.
// The size of the array can be set only once.
// Use CArrayBuf rather than one of the other container classes if RecordType
// has a constructor or destructor.
template <class RecordType>
class CArrayBuf {
private:
RecordType * buffer; // Dynamically allocated memory
uint32 num; // Number of entries in array
CArrayBuf (CArrayBuf &); // Make private copy constructor to prevent copying
public:
CArrayBuf() { // Default constructor
num = 0;
}
~CArrayBuf() { // Destructor
if (num) delete[] buffer; // Deallocate memory. Will call RecordType destructor if any
}
void SetNum(uint32 n) { // Set size of array. May be called only once!
if (n <= num) return; // Already allocated
if (num) {
err.submit(9004); // Cannot resize because items may have destructors
}
else {
buffer = new RecordType[n]; // Allocate memory. Will call RecordType constructor if any
if (!buffer) {
err.submit(9006); // Memory allocation failed
}
else {
num = n; // Save size
memset(buffer, 0, n*sizeof(RecordType));// Initialize to zero
}
}
}
uint32 GetNumEntries() {
return num; // Read size
}
RecordType & operator[] (uint32 i) { // Access array element [i]
if (i >= num) {
err.submit(9003); i = 0; // Error: index out of range
}
return buffer[i];
}
void SetZero() { // Set all items in array to 0
memset(buffer, 0, num*sizeof(RecordType)); // Warning: overwrites all members of RecordType with 0
}
};
// Class CSList<RecordType> is used for dynamic arrays where all records
// have the same type RecordType. The list can be sorted if desired.
//
// An array defined as
// CSList<RecordType> list;
// can be used in several ways:
//
// 1. The size can be set with list.SetNum(n) where n is the maximum number of
// entries. New entries can then be added in random order with list[i] = x;
// where i < n. Unused entries will be zero.
// 2. Entries can be added sequentially with
// list.Push(x);
// The first entry will be list[0]
// 3. Entries added with method 1 or 2 can be sorted in ascending order by
// calling list.Sort();
// 4. The list can be kept sorted at all times if records are added with
// list.PushSort(x);
// The list will be kept sorted in ascending order, provided that it
// was sorted before the call to PushSort.
// 5. The list can be kept sorted at all times and without duplicates if
// records are added with list.PushUnique(x);
// The list will be sorted and without duplicates after PushUnique if
// it was so before the call to PushUnique.
// 6. Entries can be read at all times as x = list[i];
// An error will be submitted if i >= list.GetNumEntries()
// 7. A sorted list can be searched for entry x by i = list.FindFirst(x);
// or i = list.Exists(x);
//
// Requirements:
// RecordType can be a simple type, a structure or a class.
// If RecordType has a constructor or destructor then they will not be
// called properly. Use CArrayBuf instead of CSList if RecordType has
// a constructor or destructor.
// The operator < const must be defined for RecordType if any of the sorting
// features are used, i.e. Sort(), PushSort(), FindFirst(), Exists().
//
// Example:
// struct S1 { // Define RecordType
// int Index;
// int operator < (S1 const & x) const { // Define operator <
// return Index < x.Index;}
// };
// CSList<S1> list; // Make list
// S1 a; a.Index = 5; // Make record
// list.PushUnique(a); // Put record into list
template <class RecordType>
class CSList : private CMemoryBuffer {
public:
void Push(RecordType const & x) {
// Add member to list
CMemoryBuffer::Push(&x, sizeof(x));
}
void PushZero() {
// Add blank entry to list
CMemoryBuffer::Push(0, sizeof(RecordType));
}
void SetNum(uint32 n) {
// Reserve space for n entries. Fill with zeroes
SetSize(n * sizeof(RecordType));
NumEntries = n; DataSize = n * sizeof(RecordType);
}
uint32 GetNumEntries() {
// Get number of entries
return NumEntries;
}
RecordType & operator[] (uint32 i) {
// Get entries by operator [] as for an array
if (i >= NumEntries) {
err.submit(9003); i = 0;} // Error: index out of range
return *(RecordType*)(Buf() + i * sizeof(RecordType));
}
void Sort() {
// Sort list by ascending RecordType items
// Operator < must be defined for RecordType
// Simple Bubble sort:
int32 i, j;
RecordType temp, * p1, * p2;
for (i = 0; i < (int32)NumEntries; i++) {
for (j = 0; j < (int32)NumEntries - i - 1; j++) {
p1 = (RecordType*)(Buf() + j * sizeof(RecordType));
p2 = (RecordType*)(Buf() + (j+1) * sizeof(RecordType));
if (*p2 < *p1) {
// Swap records
temp = *p1; *p1 = *p2; *p2 = temp;
}
}
}
}
int32 FindFirst(RecordType const & x) {
// Returns index to first record >= x.
// Returns 0 if x is smaller than all entries.
// Returns NumEntries if x is bigger than all entries. Note that this
// is not a valid index into the list.
// List must be sorted before calling FindFirst
uint32 a = 0; // Start of search interval
uint32 b = NumEntries; // End of search interval + 1
uint32 c = 0; // Middle of search interval
// Binary search loop:
while (a < b) {
c = (a + b) / 2;
if ((*this)[c] < x) {
a = c + 1;}
else {
b = c;}
}
return (int32)a;
}
int32 Exists(RecordType const & x) {
// Returns the record number if a record equal to x exists in the list.
// Returns -1 if not. The list must be sorted before calling Exists.
// Two records a and b are assumed to be equal if !(a < b || b < a)
uint32 i = FindFirst(x);
if (i == NumEntries) return -1;
if (x < (*this)[i]) return -1; else return i;
}
int32 PushSort(RecordType const & x) {
// Add member to list and keep the list sorted.
// If the list is sorted before calling PushSort then it will also be
// sorted after the call. If x is equal to an existing entry then x
// will be inserted before the existing entry.
// Operator < must be defined for RecordType.
int32 i = FindFirst(x); // Find where to insert x
int32 RecordsToMove = (int32)NumEntries-i; // Number of records to move
SetNum(NumEntries + 1); // Make space for one more record
// Move subsequent entries up one place
if (RecordsToMove > 0) {
memmove(Buf() + i * sizeof(RecordType) + sizeof(RecordType),
Buf() + i * sizeof(RecordType),
RecordsToMove * sizeof(RecordType));
}
// Insert x at position i
(*this)[i] = x;
return i;
}
int32 PushUnique(RecordType const & x) {
// Add member to list and keep the list sorted. Avoids duplicate entries.
// PushUnique will insert x in the list and keep the list sorted.
// If an entry equal to x already exists in the list then x is not
// inserted, and the return value will be the index to the existing entry.
// If no entry equal to x existed then x is inserted and the return
// value is the index to the new entry.
// This list must be sorted and without duplicates before calling
// PushUnique.
// Operator < must be defined for RecordType.
int32 i = FindFirst(x); // Find where to insert x
if (i < (int32)NumEntries && !(x < (*this)[i])) {
return i; // Duplicate found. Return index
}
int32 RecordsToMove = (int32)NumEntries-i; // Number of records to move
SetNum(NumEntries + 1); // Make space for one more record
// Move subsequent entries up one place
if (RecordsToMove > 0) {
memmove(Buf() + i * sizeof(RecordType) + sizeof(RecordType),
Buf() + i * sizeof(RecordType),
RecordsToMove * sizeof(RecordType));
}
// Insert x at position i
(*this)[i] = x;
// Return index
return i;
}
void Remove(uint32 index) {
// Remove record with this index
if (index >= NumEntries) return; // Index out of range
uint32 RecordsToMove = NumEntries - index - 1; // Number of records to move
// Move subsequent entries down one place
if (RecordsToMove > 0) {
memmove(Buf() + index * sizeof(RecordType),
Buf() + index * sizeof(RecordType) + sizeof(RecordType),
RecordsToMove * sizeof(RecordType));
}
// Count down number of entries
SetNum(NumEntries - 1);
}
};
#endif // #ifndef CONTAINERS_H