/*
* Copyright (c) 2007 GE Global Research
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
// The central idea is to add a mechanism for passing complex data structures
// efficiently between C/C++ and FreeMat .m files. It will consist of these
// new functions
// cstruct_define('typename','field1','type1','field2','type2',...)
// where 'fieldn' is the name of the field in the data structure
// and 'typen' is a typespec for the field (same as used for the import command)
// s = cstruct_thaw(membuffer, 'typename')
// where membuffer is a buffer of raw bytes (uint8) containing the data structure as laid out
// by the C/C++ compiler (may need some extra arguments to handle element alignment, not sure)
// The output is a first class struct in FreeMat. Enumerations are mapped to strings.
// membuffer = cstruct_freeze(s, 'typename')
// where s is a struct (that should correspond to the info in 'typename', and membuffer is the
// array of raw bytes that can then be passed to a C/C++ struct and cast in the C code
// to a pointer of the appropriate type.
//
// The data structure to capture the type maps is fairly simple. The typenames are
// legal strings, so we can use a SymbolTable or a STL Hash or a QStringHash. The field/type
// info are parsed and stored in a data structure to speed access. The format for the type
// info is borrowed from import. It consists of a spec of:
// typespec = <typename>[array spec]
// where array spec is either a constant or another field in the structure (or substructure) that
// contains the size of the array. For example, the following C structure:
// typedef struct {
// int len;
// char p;
// } simple_string;
// would be mapped to the following cstruct_define call:
// cstruct_define('simple_string','len','int32','p','int8[len]')
// which allows us to handle the case of "dynamic" structures - used in some notable cases to
// store variable-sized data structures in structs in C. Note that the problems of structure
// alignment and padding are _not_ solved for you. If padding is required, you must specify
// it. For example, suppose your C code converts the following structure:
// typedef struct {
// char achar;
// double d;
// } t_struct;
// into
// typedef struct {
// char achar;
// char pad1[7];
// double d;
// } t_struct;
// where the double entry has to be 8-byte aligned (for example). There is no way for FreeMat to
// determine this at run time (indeed, such factors can sometimes be affected by C compiler options
// as well). It is your responsibility to make sure the alignment is correct by inserting pads as
// necessary. Sorry, but there is no real way around this problem.
//
// Need to add arrays of structures
#include "Array.hpp"
#include "Interpreter.hpp"
#include <string>
#include <vector>
#include <map>
using namespace std;
// Base of the type system
class Ctype {
// Nothing interesting here...
public:
virtual ~Ctype() {}
virtual void print(Interpreter *m_eval) = 0;
virtual void freeze(QByteArray& out, Array s, int length, Interpreter* m_eval) = 0;
virtual Array thaw(QByteArray& input, int& pos, int length, Interpreter* m_eval) = 0;
virtual size_t size(int length) = 0;
};
// Type table
class CTable : public map<string, Ctype*> {
public:
bool contains(string name) {
return (count(name) > 0);
}
Ctype* lookup(string name) {
if (!contains(name)) throw Exception("Request for lookup of unknown type " + name);
return find(name)->second;
}
void add(string name, Ctype* val) {
if (count(name) > 0) {
delete find(name)->second;
}
(*this)[name] = val;
}
CTable();
};
static CTable CtypeTable;
// A builtin type
class Cbuiltin : public Ctype {
Class dataClass;
size_t t_size;
public:
Cbuiltin(Class i_Class, size_t i_size) : dataClass(i_Class), t_size(i_size) {}
Class getDataClass() { return dataClass; }
size_t getSize() { return t_size;}
void print(Interpreter *m_eval) {
m_eval->outputMessage("built in\n");
}
void freeze(QByteArray& out, Array s, int length, Interpreter* m_eval) {
s.promoteType(dataClass);
if (s.getLength() > length)
throw Exception("field length overflow");
const char *cp = (const char*) s.getDataPointer();
int s_len = s.getLength();
for (int i=0;i<s_len*t_size;i++)
out.push_back(cp[i]);
for (int i=s_len*t_size;i<length*t_size;i++)
out.push_back((char) 0);
}
Array thaw(QByteArray& input, int& pos, int length, Interpreter* m_eval) {
int bytecount = length*t_size;
char* dp = (char*) Array::allocateArray(dataClass,length);
if (input.size() < (pos+bytecount))
throw Exception("source buffer is too short");
for (int i=0;i<bytecount;i++)
dp[i] = input.at(pos+i);
pos += bytecount;
return Array::Array(dataClass, Dimensions(1,length), dp);
}
size_t size(int length) {
return (length*t_size);
}
};
// An enumerated type
class Cenum : public Ctype {
map<int,string> elementsByInt;
map<string,int> elementsByName;
public:
string lookupByNumber(int n) {
if (elementsByInt.count(n) == 0) return "unknown";
return elementsByInt.find(n)->second;
}
int lookupByName(string name) {
if (elementsByName.count(name) == 0) return 0;
return elementsByName.find(name)->second;
}
void addPair(string name, int value) {
elementsByInt[value] = name;
elementsByName[name] = value;
}
void print(Interpreter *m_eval) {
m_eval->outputMessage("enumeration\n");
for (map<int,string>::const_iterator i=elementsByInt.begin();
i != elementsByInt.end(); i++)
m_eval->outputMessage(" %-30s : %d\n",i->second.c_str(),i->first);
}
void freeze(QByteArray& out, Array s, int length, Interpreter* m_eval) {
if (s.isIntegerClass() && !s.isString() && (s.getLength() == length)) {
CtypeTable.lookup("int32")->freeze(out,s,length,m_eval);
return;
}
if (length == 1) {
if (!s.isString()) throw Exception("Expected string for enumerated type");
CtypeTable.lookup("int32")->freeze(out,Array::int32Constructor(lookupByName(ArrayToString(s))),
1,m_eval);
} else {
if (s.dataClass() != FM_CELL_ARRAY)
throw Exception("Expected a cell array of strings for enumerated type");
if (s.getLength() != length)
throw Exception("Length mismatch between cell array of strings and requested enumerated type");
const Array *dp = (const Array *) s.getDataPointer();
for (int i=0;i<length;i++) {
if (dp[i].isIntegerClass() && !dp[i].isString())
CtypeTable.lookup("int32")->freeze(out,s,length,m_eval);
else if (dp[i].isString())
CtypeTable.lookup("int32")->freeze(out,
Array::int32Constructor(lookupByName(ArrayToString(dp[i]))),
1,m_eval);
else
throw Exception("Expected strings as elements of cell array for enumerated type");
}
}
}
Array thaw(QByteArray& input, int& pos, int length, Interpreter* m_eval) {
Array values(CtypeTable.lookup("int32")->thaw(input,pos,length,m_eval));
if (length == 1)
return Array::stringConstructor(lookupByNumber(ArrayToInt32(values)));
const int32* dp = (const int32*) values.getDataPointer();
ArrayVector vals;
for (int i=0;i<length;i++)
vals << Array::stringConstructor(lookupByNumber(dp[i]));
return Array::cellConstructor(vals);
}
size_t size(int length) {
return CtypeTable.lookup("int32")->size(length);
}
};
// Field in a structure
class CstructField {
string name;
string type;
int length;
public:
CstructField(string i_name, string i_type, int i_length) : name(i_name),
type(i_type),
length(i_length) {}
CstructField() {}
string getName() {return name;}
string getType() {return type;}
int getLength() {return length;}
};
// Structure type
class Cstruct : public Ctype {
vector<CstructField*> fields;
public:
~Cstruct() {
for (int i=0;i<fields.size();i++) delete fields[i];
}
int getFieldCount() {return fields.size();}
CstructField* getField(int num) {return fields[num];}
void addField(CstructField* fld) {fields.push_back(fld);}
void print(Interpreter *m_eval) {
m_eval->outputMessage("struct\n");
for (int i=0;i<fields.size();i++) {
m_eval->outputMessage(" %-30s %s",fields[i]->getName().c_str(),
fields[i]->getType().c_str());
if (fields[i]->getLength() > 1)
m_eval->outputMessage("[%d]",fields[i]->getLength());
m_eval->outputMessage("\n");
}
}
void freeze(QByteArray& out, Array s, int length, Interpreter* m_eval) {
int s_count = s.getLength();
if (s_count != length)
throw Exception("Length mismatch between expected and actual array count");
for (int m=0;m<s_count;m++) {
Array m_index(Array::int32Constructor(m+1));
Array s_el(s.getVectorSubset(m_index,m_eval));
for (int i=0;i<fields.size();i++)
try {
CtypeTable.lookup(fields[i]->getType())->freeze(out,s_el.getField(fields[i]->getName()),
fields[i]->getLength(),m_eval);
} catch (Exception& e) {
throw Exception("Failed during freeze of struct field " + fields[i]->getName() + " reason: " + e.getMessageCopy());
}
}
}
Array thaw(QByteArray& input, int& pos, int length, Interpreter* m_eval) {
// Set up a set of vectors
ArrayMatrix mat;
for (int i=0;i<fields.size();i++)
mat.push_back(ArrayVector());
// collect the field names
rvstring names;
for (int i=0;i<fields.size();i++)
names << fields[i]->getName();
// Populate the matrix
for (int m=0;m<length;m++)
for (int i=0;i<fields.size();i++)
mat[i] << CtypeTable.lookup(fields[i]->getType())->thaw(input,pos,fields[i]->getLength(),m_eval);
ArrayVector t;
for (int i=0;i<fields.size();i++)
t << Array::cellConstructor(mat[i]);
return Array::structConstructor(names,t);
}
size_t size(int length) {
size_t accum = 0;
for (int i=0;i<fields.size();i++)
accum += CtypeTable.lookup(fields[i]->getType())->size(fields[i]->getLength());
return accum*length;
}
};
// Alias type
class Calias : public Ctype {
string alias;
public:
Calias(string i_alias) : alias(i_alias) {}
string getAlias() {return alias;}
void freeze(QByteArray& out, Array s, int length, Interpreter* m_eval) {
CtypeTable.lookup(alias)->freeze(out,s,length,m_eval);
}
Array thaw(QByteArray& input, int& pos, int length, Interpreter* m_eval) {
return CtypeTable.lookup(alias)->thaw(input,pos,length,m_eval);
}
size_t size(int length) {
return CtypeTable.lookup(alias)->size(length);
}
void print(Interpreter *m_eval) {
m_eval->outputMessage("alias to " + alias + "\n");
}
};
CTable::CTable() {
add("uint8",new Cbuiltin(FM_UINT8,sizeof(uint8)));
add("uint16",new Cbuiltin(FM_UINT16,sizeof(uint16)));
add("uint32",new Cbuiltin(FM_UINT32,sizeof(uint32)));
add("uint64",new Cbuiltin(FM_UINT64,sizeof(uint64)));
add("int8",new Cbuiltin(FM_INT8,sizeof(int8)));
add("int16",new Cbuiltin(FM_INT16,sizeof(int16)));
add("int32",new Cbuiltin(FM_INT32,sizeof(int32)));
add("int64",new Cbuiltin(FM_INT64,sizeof(int64)));
add("float",new Cbuiltin(FM_FLOAT,sizeof(float)));
add("double",new Cbuiltin(FM_DOUBLE,sizeof(double)));
}
//!
//@Module CENUM Lookup Enumerated C Type
//@@Section EXTERNAL
//@@Usage
//The @|cenum| function allows you to use the textual strings of C
//enumerated types (that have been defined using @|ctypedefine|) in
//your FreeMat scripts isntead of the hardcoded numerical values. The
//general syntax for its use is
//@[
// enum_int = cenum(enum_type,enum_string)
//@]
//which looks up the integer value of the enumerated type based on the
//string. You can also supply an integer argument, in which case
//@|cenum| will find the matching string
//@[
// enum_string = cenum(enum_type,enum_int)
//@]
//!
ArrayVector CenumFunction(int nargout, const ArrayVector& arg) {
if (arg.size() < 2) throw Exception("cenum requires at least two arguments");
string enumname = ArrayToString(arg[0]);
if (!CtypeTable.contains(enumname))
throw Exception("type " + enumname + " is not defined");
Ctype* p = CtypeTable.lookup(enumname);
Cenum* q = dynamic_cast<Cenum*>(p);
if (!q)
throw Exception("type " + enumname + " is not an enumerated type");
if (arg[1].isString()) {
return ArrayVector() << Array::int32Constructor(q->lookupByName(ArrayToString(arg[1])));
} else {
return ArrayVector() << Array::stringConstructor(q->lookupByNumber(ArrayToInt32(arg[1])));
}
return ArrayVector();
}
//!
//@Module CTYPEDEFINE Define C Type
//@@Section EXTERNAL
//@@Usage
//The @|ctypedefine| function allows you to define C types for use
//with FreeMat. Three variants of C types can be used. You can
//use structures, enumerations, and aliases (typedefs). All three are defined
//through a single function @|ctypedefine|. The general syntax for
//its use is
//@[
// ctypedefine(typeclass,typename,...)
//@]
//where @|typeclass| is the variant of the type (legal values are
//@|'struct'|, @|'alias'|, @|'enum'|). The second argument is the
//name of the C type. The remaining arguments depend on what the
//class of the typedef is.
//
//To define a C structure, use the @|'struct'| type class. The usage
//in this case is:
//@[
// ctypedefine('struct',typename,field1,type1,field2,type2,...)
//@]
//The argument @|typename| must be a valid identifier string. Each of
//of the @|field| arguments is also a valid identifier string that
//describe in order, the elements of the C structure. The @|type| arguments
//are @|typespecs|. They can be of three types:
//\begin{itemize}
//\item Built in types, e.g. @|'uint8'| or @|'double'| to name a couple of
// examples.
//\item C types that have previously been defined with a call to
// @|ctypedefine|, e.g. @|'mytype'| where @|'mytype'| has already been
// defined through a call to @|ctypedefine|.
//\item Arrays of either built in types or previously defined C types
// with the length of the array coded as an integer in square brackets,
// for example: @|'uint8[10]'| or @|'double[1000]'|.
//\end{itemize}
//
//To define a C enumeration, use the @|'enum'| type class. The usage
//in this case is:
// ctypedefine('enum',typename,name1,value1,name2,value2,...)
//@]
//The argument @|typename| must be a valid identifier string. Each of the
//@|name| arguments must also be valid identifier strings that describe
//the possible values that the enumeration can take an, and their corresponding
//integer values. Note that the names should be unique. The behavior of
//the various @|cenum| functions is undefined if the names are not unique.
//
//To define a C alias (or typedef), use the following form of @|ctypedefine|:
//@[
// ctypedefine('alias',typename,aliased_typename)
//@]
//where @|aliased_typename| is the type that is being aliased to.
//!
ArrayVector CtypeDefineFunction(int nargout, const ArrayVector& arg) {
if (arg.size() < 1) return ArrayVector();
if (arg.size() < 3) throw Exception("ctypedefine requires at least three arguments - the typeclass ('struct','alias','enum'), the typename, and some type definition information");
string tclass = ArrayToString(arg[0]);
if (tclass == "enum") {
string tname = ArrayToString(arg[1]);
int cnt = 2;
Cenum *enumDef = new Cenum;
while (cnt < arg.size()) {
string ftype = ArrayToString(arg[cnt]);
if (arg.size() < (cnt+1))
throw Exception("Expecting value for enum name " + ftype);
int fvalue = ArrayToInt32(arg[cnt+1]);
enumDef->addPair(ftype,fvalue);
cnt += 2;
}
CtypeTable.add(tname,enumDef);
} else if (tclass == "struct") {
string tname = ArrayToString(arg[1]);
int cnt = 2;
Cstruct *new_struct = new Cstruct;
while (cnt < arg.size()) {
string ftype = ArrayToString(arg[cnt]);
if (arg.size() < (cnt+1))
throw Exception("Expecting typespec for fieldname " + ftype);
string ttypespec = ArrayToString(arg[cnt+1]);
string ttype;
int tlength;
if (ttypespec.find('[') != ttypespec.npos) {
int left_brace = ttypespec.find('[');
int right_brace = ttypespec.find(']');
ttype = ttypespec.substr(0,left_brace);
tlength = atoi(ttypespec.substr(left_brace+1,right_brace-left_brace-1).c_str());
if (tlength <= 0)
throw Exception(string("illegal length of ") + tlength + " in defining type " + tname);
} else {
ttype = ttypespec;
tlength = 1;
}
if (!CtypeTable.contains(ttype))
throw Exception("type " + ttype + " is not defined");
new_struct->addField(new CstructField(ftype,ttype,tlength));
cnt += 2;
}
CtypeTable.add(tname,new_struct);
} else if (tclass == "alias") {
string tname = ArrayToString(arg[1]);
string aname = ArrayToString(arg[2]);
if (tname == aname)
throw Exception("self-referencing aliases not allowed: " + tname);
if (!CtypeTable.contains(aname))
throw Exception("type " + aname + " is not defined");
CtypeTable.add(tname,new Calias(aname));
}
return ArrayVector();
}
//!
//@Module CTYPEPRINT Print C Type
//@@Section EXTERNAL
//@@Usage
//The @|ctypeprint| function prints a C type on the console. The
//usage is
//@[
// ctypeprint(typename)
//@]
//where @|typename| is a string containing the name of the C type to print.
//Depending on the class of the C type (e.g., structure, alias or enumeration)
//the @|ctypeprint| function will dump information about the type definition.
//!
ArrayVector CtypePrintFunction(int nargout, const ArrayVector& arg, Interpreter *m_eval) {
if (arg.size() < 1) return ArrayVector();
string tname = ArrayToString(arg[0]);
if (!CtypeTable.contains(tname)) {
m_eval->outputMessage("ctype " + tname + " not in table");
return ArrayVector();
}
m_eval->outputMessage("ctype: " + tname + " ");
CtypeTable.lookup(tname)->print(m_eval);
return ArrayVector();
}
//!
//@Module CTYPEFREEZE Convert FreeMat Structure to C Type
//@@Section EXTERNAL
//@@Usage
//The @|ctypefreeze| function is used to convert a FreeMat structure into
//a C struct as defined by a C structure typedef. To use the @|cstructfreeze|
//function, you must first define the type of the C structure using the
//@|ctypedefine| function. The @|ctypefreeze| function then serializes
//a FreeMat structure to a set of bytes, and returns it as an array. The
//usage for @|ctypefreeze| is
//@[
// byte_array = ctypefreeze(mystruct, 'typename')
//@]
//where @|mystruct| is the array we want to 'freeze' to a memory array,
//and @|typename| is the name of the C type that we want the resulting
//byte array to conform to.
//!
ArrayVector CtypeFreezeFunction(int nargout, const ArrayVector& arg, Interpreter* m_eval) {
if (arg.size() != 2)
throw Exception("ctypefreeze requires two arguments - the structure to freeze and the typename to use");
Array s(arg[0]);
string ttype(ArrayToString(arg[1]));
if (!CtypeTable.contains(ttype))
throw Exception("unable to find a C type definition for " + ttype);
QByteArray output;
CtypeTable.lookup(ttype)->freeze(output,s,s.getLength(),m_eval);
uint8* dp = (uint8*) Array::allocateArray(FM_UINT8, output.length());
memcpy(dp,output.constData(),output.length());
return ArrayVector() << Array::Array(FM_UINT8,Dimensions(1,output.length()),dp);
}
//!
//@Module CTYPESIZE Compute Size of C Struct
//@@Section EXTERNAL
//@@Usage
//The @|ctypesize| function is used to compute the size of a C structure
//that is defined using the @|ctypedefine| function. The usage of
//@|ctypesize| is
//@[
// size = ctypesize('typename')
//@]
//where @|typename| is the name of the C structure you want to compute
//the size of. The returned count is measured in bytes. Note that as
//indicated in the help for @|ctypedefine| that FreeMat does not
//automatically pad the entries of the structure to match the particulars
//of your C compiler. Instead, the assumption is that you have adequate
//padding entries in your structure to align the FreeMat members with the
//C ones. See @|ctypedefine| for more details. You can also specify
//an optional count parameter if you want to determine how large multiple
//structures are
//@[
// size = ctypesize('typename',count)
//@]
//!
ArrayVector CtypeSizeFunction(int nargout, const ArrayVector& arg) {
if (arg.size() < 1)
throw Exception("ctypesize requires an argument - the structure name");
int count = 1;
if (arg.size() > 1)
count = ArrayToInt32(arg[1]);
string ttype(ArrayToString(arg[0]));
if (!CtypeTable.contains(ttype))
throw Exception("unable to find a C struct definition for type " + ttype);
return ArrayVector() << Array::int32Constructor(CtypeTable.lookup(ttype)->size(count));
}
//!
//@Module CTYPETHAW Convert C Struct to FreeMat Structure
//@@Section EXTERNAL
//@@Usage
//The @|ctypethaw| function is used to convert a C structure that is
//encoded in a byte array into a FreeMat structure using a C structure
//typedef. To use the @|ctypethaw| function, you must first define
//the type of the C structure using the @|ctypedefine| function. The
//usage of @|ctypethaw| is
//@[
// mystruct = ctypethaw(byte_array, 'typename')
//@]
//where @|byte_array| is a @|uint8| array containing the bytes that encode
//the C structure, and @|typename| is a string that contains the type
//description as registered with @|ctypedefine|. If you want to
//retrieve multiple structures from a single byte array, you can specify
//a count as
//@[
// mystruct = ctypethaw(byte_array, 'typename', count)
//@]
//where @|count| is an integer containing the number of structures to
//retrieve. Sometimes it is also useful to retrieve only part of the
//structure from a byte array, and then (based on the contents of the
//structure) retrieve more data. In this case, you can retrieve the
//residual byte array using the optional second output argument of
//@|ctypethaw|:
//@[
// [mystruct,byte_array_remaining] = ctypethaw(byte_array, 'typename',...)
//@]
//!
ArrayVector CtypeThawFunction(int nargout, const ArrayVector& arg, Interpreter* m_eval) {
if (arg.size() < 2)
throw Exception("ctypethaw requires two arguments - the uint8 array to thaw the structure from, and the typename to use");
Array s(arg[0]);
string ttype(ArrayToString(arg[1]));
int count = 1;
if (arg.size() > 2) count = ArrayToInt32(arg[2]);
if (!CtypeTable.contains(ttype))
throw Exception("unable to find a C struct definition for type " + ttype);
if (s.dataClass() != FM_UINT8)
throw Exception("first argument to ctypethaw must be a uint8 array");
QByteArray input((const char*) s.getDataPointer(),s.getLength());
int pos = 0;
ArrayVector outputs;
outputs << CtypeTable.lookup(ttype)->thaw(input,pos,count,m_eval);
if (nargout > 1) {
input.remove(0,pos);
uint8* dp = (uint8*) Array::allocateArray(FM_UINT8, input.length());
memcpy(dp,input.constData(),input.length());
outputs << Array::Array(FM_UINT8,Dimensions(1,input.length()),dp);
}
return outputs;
}
syntax highlighted by Code2HTML, v. 0.9.1