TestPackage/LTemplate/IncludeFiles/mlstream.h
2022-06-23 14:25:05 +02:00

442 lines
14 KiB
C++

/*
* Copyright (c) 2019 Szabolcs Horvát.
*
* See the file LICENSE.txt for copying permission.
*/
#ifndef MLSTREAM_H
#define MLSTREAM_H
/** \file mlstream.h
* \brief Auxiliary header for LTemplate to ease reading function arguments and returning values through MathLink.
*
* LTemplate itself does not depend on mlstream.h, so if you don't use this header,
* feel free to remove it from your project. mlstream.h is not meant as a general
* MathLink interface. It is specifically designed for handling arguments and return
* values in conjunction with LTemplate and `LinkObject`-based functions.
* To do this, mlstream functions are usually called in a specific sequence, as
* illustrated below.
*
* Example usage:
* \code
* void addMult(MLINK link) {
* mlStream ml(link, "addMult"); // any errors messages will mention the context "addMult"
*
* int i, j;
* ml >> mlCheckArgs(2) // read off the head List and check argument count
* >> i >> j; // read two integer arguments
*
* // compute the result
* int sum = i+j;
* int prod = i*j;
*
* // alias for MLNewPacket, must be used before returning the result
* ml.newPacket();
*
* // we return two results in a list
* ml << mlHead("List", 2)
* << sum << prod;
* }
* \endcode
*
* See `Documentation/Examples/LinkObject` for more examples.
*
* ----
*
* Currently, mlstream.h has direct support for sending and receiving the following types:
*
* **Sending**
*
* - Signed integers (16-, 32- and 64-bit)
* - Floating point numbers
* - Strings (`std::string` or null-terminated C string)
* - `mma::RealTensorRef` and `mma::IntTensorRef` of arbitrary dimensions
* - `std::vector` or `std::list` holding any supported type (with optimization for `std::vector` holding numerical types)
* - `std::pair` holding any two supported types
* - Symbols (mlSymbol) or functions (mlHead)
*
* **Receiving**
*
* - Signed integers (16-, 32- and 64-bit)
* - Floating point numbers
* - Strings (`std::string` only)
* - `std::vector` holding any supported type (with optimization for numerical types)
*/
#include "LTemplate.h"
#include <vector>
#include <list>
#include <utility>
#include <string>
#include <sstream>
#include <type_traits>
// Sanity checks for the sizes of MathLink integer types.
static_assert(sizeof(short) == 2, "MathLink type size mismatch: sizeof(short) != 2.");
static_assert(sizeof(int) == 4 , "MathLink type size mismatch: sizeof(int) != 4.");
static_assert(sizeof(mlint64) == 8, "MathLink type size mismatch: sizeof(mlint64) != 8.");
/** \brief Wrapper for `MLINK` to allow using extractors and inserters
*
* \param link is the MLINK object to wrap
* \param context is a string that will be prepended to any message reported using error()
*/
class mlStream {
MLINK lp;
std::string context;
public:
explicit mlStream(MLINK link) : lp(link) { }
mlStream(MLINK link, const std::string &context) : lp(link), context(context) { }
/// Retrieve the stored `MLINK`
MLINK link() { return lp; }
/// Throws a \ref mma::LibraryError with a given message.
[[ noreturn ]] void error(const std::string &err) {
std::ostringstream msg;
if (! context.empty())
msg << context << ": ";
msg << err << ".";
throw mma::LibraryError(msg.str());
}
/// Equivalent to `MLNewPacket()`
void newPacket() {
MLNewPacket(lp);
}
};
// Special
/// Must be the first item extracted from an mlStream, checks number of arguments and prepares for reading them.
struct mlCheckArgs {
int argc;
explicit mlCheckArgs(int argc) : argc(argc) { }
};
inline mlStream & operator >> (mlStream &ml, const mlCheckArgs &ca) {
int count;
if (! MLTestHead(ml.link(), "List", &count))
ml.error("argument check: head \"List\" expected");
if (count != ca.argc){
std::ostringstream msg;
msg << ca.argc << " argument" << (ca.argc == 1 ? "" : "s") << " expected, " << count << " received";
ml.error(msg.str());
}
return ml;
}
/** \brief Used for inserting a head with the given argument count into an mlStream.
*
* Typically used with the head `List` when returning multiple results.
*
* The following example returns the complex number `3 - 2I`.
*
* \code
* ml << mlHead("Complex", 2) << 3 << -2;
* \endcode
*/
struct mlHead {
const char *head;
int argc;
mlHead(const char *head, int argc) : head(head), argc(argc) { }
};
inline mlStream & operator << (mlStream &ml, const mlHead &head) {
if (! MLPutFunction(ml.link(), head.head, head.argc)) {
std::ostringstream msg;
msg << "Cannot put head " << head.head << " with " << head.argc << " arguments";
ml.error(msg.str());
}
return ml;
}
/** \brief Used for inserting a symbol into an mlStream
*
* The following example returns `True` or `False` based on a Boolean variable.
*
* \code
* mlStream ml(link);
* bool b;
* // ...
* ml.newPacket();
* ml << (b ? mlSymbol("True") : mlSymbol("False"));
* \endcode
*
* While this is convenient for a single result, Boolean arrays are much faster to transfer as integers.
*/
struct mlSymbol {
const char *symbol;
explicit mlSymbol(const char *symbol) : symbol(symbol) { }
};
inline mlStream & operator << (mlStream &ml, const mlSymbol &symbol) {
if (! MLPutSymbol(ml.link(), symbol.symbol)) {
std::ostringstream msg;
msg << "Cannot put symbol " << symbol.symbol;
ml.error(msg.str());
}
return ml;
}
/** \brief Used for discarding a given number of expressions from an mlStream
*
* The following example reads 3 arguments, but does not use the second one.
*
* \code
* mlStream ml(link);
* ml >> mlCheckArgs(3) >> x >> mlDiscard() >> y;
* \endcode
*/
struct mlDiscard {
const int count;
explicit mlDiscard(int count = 1) : count(count) { }
};
inline mlStream & operator >> (mlStream &ml, const mlDiscard &drop) {
for (int i=0; i < drop.count; ++i)
if (! MLTransferExpression(nullptr, ml.link()))
ml.error("Cannot discard expression");
return ml;
}
// Basic types (integer and floating point)
#define MLSTREAM_DEF_BASIC_GET_INTEGRAL(MTYPE, CTYPE) \
template<typename T, \
typename std::enable_if<std::is_integral<T>::value && std::is_signed<T>::value && sizeof(T) == sizeof(CTYPE), int>::type = 0 > \
inline mlStream & operator >> (mlStream &ml, T &x) { \
if (! MLGet ## MTYPE(ml.link(), reinterpret_cast<CTYPE *>(&x))) \
ml.error(#MTYPE " expected"); \
return ml; \
}
MLSTREAM_DEF_BASIC_GET_INTEGRAL(Integer16, short)
MLSTREAM_DEF_BASIC_GET_INTEGRAL(Integer32, int)
MLSTREAM_DEF_BASIC_GET_INTEGRAL(Integer64, mlint64)
#define MLSTREAM_DEF_BASIC_GET(MTYPE, CTYPE) \
inline mlStream & operator >> (mlStream &ml, CTYPE &x) { \
if (! MLGet ## MTYPE(ml.link(), &x)) \
ml.error(#MTYPE " expected"); \
return ml; \
}
MLSTREAM_DEF_BASIC_GET(Real32, float)
MLSTREAM_DEF_BASIC_GET(Real64, double)
MLSTREAM_DEF_BASIC_GET(Real128, mlextended_double)
#define MLSTREAM_DEF_BASIC_PUT_INTEGRAL(MTYPE, CTYPE) \
template<typename T, \
typename std::enable_if<std::is_integral<T>::value && std::is_signed<T>::value && sizeof(T) == sizeof(CTYPE), int>::type = 0 > \
inline mlStream & operator << (mlStream &ml, T x) { \
if (! MLPut ## MTYPE(ml.link(), static_cast<CTYPE>(x))) \
ml.error("Cannot return " #MTYPE); \
return ml; \
}
MLSTREAM_DEF_BASIC_PUT_INTEGRAL(Integer16, short)
MLSTREAM_DEF_BASIC_PUT_INTEGRAL(Integer32, int)
MLSTREAM_DEF_BASIC_PUT_INTEGRAL(Integer64, mlint64)
#define MLSTREAM_DEF_BASIC_PUT(MTYPE, CTYPE) \
inline mlStream & operator << (mlStream &ml, CTYPE x) { \
if (! MLPut ## MTYPE(ml.link(), x)) \
ml.error("Cannot return " #MTYPE); \
return ml; \
}
MLSTREAM_DEF_BASIC_PUT(Real32, float)
MLSTREAM_DEF_BASIC_PUT(Real64, double)
MLSTREAM_DEF_BASIC_PUT(Real128, mlextended_double)
// Strings
inline mlStream & operator >> (mlStream &ml, std::string &s) {
const unsigned char *sp;
int bytes, chars;
if (! MLGetUTF8String(ml.link(), &sp, &bytes, &chars))
ml.error("String expected");
s.assign(reinterpret_cast<const char *>(sp), bytes);
MLReleaseUTF8String(ml.link(), sp, bytes);
return ml;
}
inline mlStream & operator << (mlStream &ml, const std::string &s) {
if (! MLPutUTF8String(ml.link(), reinterpret_cast<const unsigned char *>(s.c_str()), s.size()))
ml.error("Cannot return UTF8 string");
return ml;
}
inline mlStream & operator << (mlStream &ml, const char *s) {
if (! MLPutString(ml.link(), s))
ml.error("Cannot return string");
return ml;
}
// TensorRef
inline mlStream & operator << (mlStream &ml, mma::IntTensorRef t) {
const int maxrank = 16;
const int rank = t.rank();
const mint *mdims = t.dimensions();
int dims[maxrank];
massert(rank <= maxrank);
std::copy(mdims, mdims + rank, dims);
#ifdef MINT_32
if (! MLPutInteger32Array(ml.link(), reinterpret_cast<int *>(t.data()), dims, NULL, rank))
ml.error("Cannot return Integer Tensor.");
#else
if (! MLPutInteger64Array(ml.link(), reinterpret_cast<mlint64 *>(t.data()), dims, nullptr, rank))
ml.error("Cannot return Integer Tensor.");
#endif
return ml;
}
inline mlStream & operator << (mlStream &ml, mma::RealTensorRef t) {
const int maxrank = 16;
const int rank = t.rank();
const mint *mdims = t.dimensions();
int dims[maxrank];
massert(rank <= maxrank);
std::copy(mdims, mdims + rank, dims);
if (! MLPutReal64Array(ml.link(), t.data(), dims, nullptr, rank))
ml.error("Cannot return Real Tensor");
return ml;
}
// TODO support complex tensors
// Standard containers -- list
template<typename T>
inline mlStream & operator << (mlStream &ml, const std::list<T> &ls) {
ml << mlHead("List", ls.size());
for (typename std::list<T>::const_iterator i = ls.begin(); i != ls.end(); ++i)
ml << *i;
return ml;
}
// Standard containers -- vector
// Put signed integer element types, 16, 32 and 64 bits.
#define MLSTREAM_DEF_VEC_PUT_INTEGRAL(MTYPE, CTYPE) \
template<typename T, \
typename std::enable_if<std::is_integral<T>::value && std::is_signed<T>::value && sizeof(T) == sizeof(CTYPE), int>::type = 0 > \
inline mlStream & operator << (mlStream &ml, const std::vector<T> &vec) { \
const CTYPE *data = vec.empty() ? nullptr : reinterpret_cast<const CTYPE *>(vec.data()); \
if (! MLPut ## MTYPE ## List(ml.link(), data, vec.size())) \
ml.error("Cannot return vector of " #MTYPE); \
return ml; \
}
MLSTREAM_DEF_VEC_PUT_INTEGRAL(Integer16, short)
MLSTREAM_DEF_VEC_PUT_INTEGRAL(Integer32, int)
MLSTREAM_DEF_VEC_PUT_INTEGRAL(Integer64, mlint64)
// Put floating point element types
#define MLSTREAM_DEF_VEC_PUT(MTYPE, CTYPE) \
inline mlStream & operator << (mlStream &ml, const std::vector<CTYPE> &vec) { \
const CTYPE *data = vec.empty() ? nullptr : vec.data(); \
if (! MLPut ## MTYPE ## List(ml.link(), data, vec.size())) \
ml.error("Cannot return vector of " #MTYPE); \
return ml; \
}
MLSTREAM_DEF_VEC_PUT(Real32, float)
MLSTREAM_DEF_VEC_PUT(Real64, double)
MLSTREAM_DEF_VEC_PUT(Real128, mlextended_double)
// Put all other types
template<typename T,
typename std::enable_if<! (std::is_integral<T>::value && std::is_signed<T>::value && (sizeof(T) == sizeof(short) || sizeof(T) == sizeof(int) || sizeof(T) == sizeof(mlint64)) ), int>::type = 0 >
inline mlStream & operator << (mlStream &ml, const std::vector<T> &vec) {
ml << mlHead("List", vec.size());
for (typename std::vector<T>::const_iterator i = vec.begin(); i != vec.end(); ++i)
ml << *i;
return ml;
}
// Get signed integer element types
#define MLSTREAM_DEF_VEC_GET_INTEGRAL(MTYPE, CTYPE) \
template<typename T, \
typename std::enable_if<std::is_integral<T>::value && std::is_signed<T>::value && sizeof(T) == sizeof(CTYPE), int>::type = 0> \
inline mlStream & operator >> (mlStream &ml, std::vector<T> &vec) { \
CTYPE *data; \
int count; \
if (! MLGet ## MTYPE ## List(ml.link(), &data, &count)) \
ml.error(#MTYPE " list expected"); \
vec.assign(data, data+count); \
MLRelease ## MTYPE ## List(ml.link(), data, count); \
return ml; \
}
MLSTREAM_DEF_VEC_GET_INTEGRAL(Integer16, short)
MLSTREAM_DEF_VEC_GET_INTEGRAL(Integer32, int)
MLSTREAM_DEF_VEC_GET_INTEGRAL(Integer64, mlint64)
// Get floating point element types
#define MLSTREAM_DEF_VEC_GET(MTYPE, CTYPE) \
inline mlStream & operator >> (mlStream &ml, std::vector<CTYPE> &vec) { \
CTYPE *data; \
int count; \
if (! MLGet ## MTYPE ## List(ml.link(), &data, &count)) \
ml.error(#MTYPE " list expected"); \
vec.assign(data, data+count); \
MLRelease ## MTYPE ## List(ml.link(), data, count); \
return ml; \
}
MLSTREAM_DEF_VEC_GET(Real32, float)
MLSTREAM_DEF_VEC_GET(Real64, double)
MLSTREAM_DEF_VEC_GET(Real128, mlextended_double)
// Get all other types
template<typename T,
typename std::enable_if<! (std::is_integral<T>::value && std::is_signed<T>::value && (sizeof(T) == sizeof(short) || sizeof(T) == sizeof(int) || sizeof(T) == sizeof(mlint64)) ), int>::type = 0 >
inline mlStream & operator >> (mlStream &ml, std::vector<T> &vec) {
int count;
if (! MLTestHead(ml.link(), "List", &count))
ml.error("Head \"List\" expected");
vec.clear();
vec.resize(count);
for (auto &el : vec)
ml >> el;
return ml;
}
// Put an std::pair
template<typename A, typename B>
inline mlStream & operator << (mlStream &ml, const std::pair<A,B> &pair) {
ml << mlHead("List", 2) << pair.first << pair.second;
return ml;
}
#endif // MLSTREAM_H