addon-tools-el/include/addon-tools.hpp

670 lines
29 KiB
C++

#ifndef ADDON_TOOLS_HPP
#define ADDON_TOOLS_HPP
#define NODE_ADDON_API_DISABLE_DEPRECATED
#define NAPI_DISABLE_CPP_EXCEPTIONS
#include <napi.h>
#ifdef _WIN32
#define strcasestr(s, t) strstr(strupr(s), strupr(t))
#endif
#ifdef _WIN32
#define DBG_EXPORT __declspec(dllexport)
#else
#define DBG_EXPORT
#endif
#define NAPI_ENV Napi::Env env = info.Env();
#define NAPI_HS Napi::HandleScope scope(env);
#define JS_UNDEFINED env.Undefined()
#define JS_NULL env.Null()
#define JS_STR(VAL) Napi::String::New(env, VAL)
#define JS_NUM(VAL) Napi::Number::New(env, static_cast<double>(VAL))
#define JS_EXT(VAL) Napi::External<void>::New(env, static_cast<void*>(VAL))
#define JS_BOOL(VAL) Napi::Boolean::New(env, static_cast<bool>(VAL))
#define JS_OBJECT Napi::Object::New(env)
#define JS_ARRAY Napi::Array::New(env)
#define RET_VALUE(VAL) return VAL;
#define RET_UNDEFINED RET_VALUE(JS_UNDEFINED)
#define RET_NULL RET_VALUE(JS_NULL)
#define RET_STR(VAL) RET_VALUE(JS_STR(VAL))
#define RET_NUM(VAL) RET_VALUE(JS_NUM(VAL))
#define RET_EXT(VAL) RET_VALUE(JS_EXT(VAL))
#define RET_BOOL(VAL) RET_VALUE(JS_BOOL(VAL))
#define JS_THROW(VAL) \
Napi::Error::New(env, VAL).ThrowAsJavaScriptException();
#define REQ_ARGS(N) \
if (info.Length() < (N)) { \
JS_THROW("Expected at least " #N " arguments"); \
RET_UNDEFINED; \
}
#define IS_EMPTY(VAL) (VAL.IsNull() || VAL.IsUndefined())
#define IS_ARG_EMPTY(I) IS_EMPTY(info[I])
#define CHECK_REQ_ARG(I, C, T) \
if (info.Length() <= (I) || !info[I].C) { \
JS_THROW("Argument " #I " must be of type `" T "`"); \
RET_UNDEFINED; \
}
#define CHECK_LET_ARG(I, C, T) \
if (!(IS_ARG_EMPTY(I) || info[I].C)) { \
JS_THROW( \
"Argument " #I \
" must be of type `" T \
"` or be `null`/`undefined`" \
); \
RET_UNDEFINED; \
}
#define REQ_STR_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsString(), "String"); \
std::string VAR = info[I].ToString().Utf8Value();
#define USE_STR_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsString(), "String"); \
std::string VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToString().Utf8Value();
#define LET_STR_ARG(I, VAR) USE_STR_ARG(I, VAR, "")
#define REQ_INT32_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Int32"); \
int VAR = info[I].ToNumber().Int32Value();
#define USE_INT32_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "Int32"); \
int VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToNumber().Int32Value();
#define LET_INT32_ARG(I, VAR) USE_INT32_ARG(I, VAR, 0)
#define REQ_INT_ARG(I, VAR) REQ_INT32_ARG(I, VAR)
#define USE_INT_ARG(I, VAR, DEF) USE_INT32_ARG(I, VAR, DEF)
#define LET_INT_ARG(I, VAR) LET_INT32_ARG(I, VAR)
#define REQ_UINT32_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Uint32"); \
unsigned int VAR = info[I].ToNumber().Uint32Value();
#define USE_UINT32_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "Uint32"); \
unsigned int VAR = IS_ARG_EMPTY(I) \
? (DEF) \
: info[I].ToNumber().Uint32Value();
#define LET_UINT32_ARG(I, VAR) USE_UINT32_ARG(I, VAR, 0)
#define REQ_UINT_ARG(I, VAR) REQ_UINT32_ARG(I, VAR)
#define USE_UINT_ARG(I, VAR, DEF) USE_UINT32_ARG(I, VAR, DEF)
#define LET_UINT_ARG(I, VAR) LET_UINT32_ARG(I, VAR)
#define REQ_BOOL_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsBoolean(), "Bool"); \
bool VAR = info[I].ToBoolean().Value();
#define USE_BOOL_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsBoolean(), "Bool"); \
bool VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToBoolean().Value();
#define LET_BOOL_ARG(I, VAR) USE_BOOL_ARG(I, VAR, false)
#define SOFT_BOOL_ARG(I, VAR) \
bool VAR = (info.Length() >= (I) && info[I].ToBoolean().Value()) || false;
#define REQ_OFFS_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Number"); \
size_t VAR = static_cast<size_t>(info[I].ToNumber().DoubleValue());
#define USE_OFFS_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "Number"); \
size_t VAR = IS_ARG_EMPTY(I) \
? (DEF) \
: static_cast<size_t>(info[I].ToNumber().DoubleValue());
#define LET_OFFS_ARG(I, VAR) USE_OFFS_ARG(I, VAR, 0)
#define REQ_DOUBLE_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Number"); \
double VAR = info[I].ToNumber().DoubleValue();
#define USE_DOUBLE_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "Number"); \
double VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToNumber().DoubleValue();
#define LET_DOUBLE_ARG(I, VAR) USE_DOUBLE_ARG(I, VAR, 0.0)
#define REQ_FLOAT_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Number"); \
float VAR = info[I].ToNumber().FloatValue();
#define USE_FLOAT_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "Number"); \
float VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToNumber().FloatValue();
#define LET_FLOAT_ARG(I, VAR) USE_FLOAT_ARG(I, VAR, 0.f)
#define REQ_EXT_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsExternal(), "Pointer"); \
void *VAR = info[I].As< Napi::External<void> >().Data();
#define USE_EXT_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsExternal(), "Pointer"); \
void *VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].As< Napi::External<void> >().Data();
#define LET_EXT_ARG(I, VAR) USE_EXT_ARG(I, VAR, nullptr)
#define REQ_FUN_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsFunction(), "Function"); \
Napi::Function VAR = info[I].As<Napi::Function>();
#define REQ_OBJ_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsObject(), "Object"); \
Napi::Object VAR = info[I].As<Napi::Object>();
#define USE_OBJ_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsObject(), "Object"); \
Napi::Object VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].As<Napi::Object>();
#define LET_OBJ_ARG(I, VAR) USE_OBJ_ARG(I, VAR, Napi::Object::New(env))
#define REQ_ARRV_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsArrayBuffer(), "ArrayBuffer"); \
Napi::ArrayBuffer VAR = info[I].As<Napi::ArrayBuffer>();
#define REQ_BUF_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsBuffer(), "Buffer"); \
Napi::Buffer<uint8_t> VAR = info[I].As< Napi::Buffer<uint8_t> >();
#define REQ_ARRAY_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsArray(), "Array"); \
Napi::Array VAR = info[I].As<Napi::Array>();
#define USE_ARRAY_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsArray(), "Array"); \
Napi::Array VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].As<Napi::Array>();
#define LET_ARRAY_ARG(I, VAR) USE_ARRAY_ARG(I, VAR, Napi::Array::New(env))
inline std::vector<std::string> arrayStrToVec(const Napi::Array &arr) {
uint32_t count = arr.Length();
std::vector<std::string> result(count);
for (uint32_t i = 0; i < count; i++) {
Napi::Value item = arr[i];
if (item.IsString()) {
result[i] = item.ToString().Utf8Value();
}
}
return result;
}
inline Napi::Array stringsToArray(Napi::Env env, const char **strings, size_t count) {
Napi::Array arr = JS_ARRAY;
for (size_t i = 0; i < count; i++) {
arr.Set(i, strings[i]);
}
return arr;
}
inline Napi::Array vecStrToArray(Napi::Env env, const std::vector<std::string> &strings) {
Napi::Array arr = JS_ARRAY;
size_t count = strings.size();
for (size_t i = 0; i < count; i++) {
arr.Set(i, strings[i]);
}
return arr;
}
#define LET_ARRAY_STR_ARG(I, VAR) \
USE_ARRAY_ARG(I, __ARRAY_ ## VAR, Napi::Array::New(env)); \
std::vector<std::string> VAR = arrayStrToVec(__ARRAY_ ## VAR);
#define RET_ARRAY_STR(VAL) RET_VALUE(vecStrToArray(env, VAL))
#define REQ_TYPED_ARRAY_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsTypedArray(), "TypedArray"); \
Napi::TypedArray VAR = info[I].As<Napi::TypedArray>();
#define DES_CHECK \
if (_isDestroyed) return;
#define THIS_CHECK \
NAPI_ENV; \
if (_isDestroyed) RET_UNDEFINED;
#define CACHE_CAS(CACHE, V) \
if (CACHE == V) { \
RET_UNDEFINED; \
} \
CACHE = V;
#define SETTER_CHECK(C, T) \
if (!value.C) { \
JS_THROW("Value must be " T); \
RET_UNDEFINED; \
}
#define JS_METHOD(NAME) Napi::Value NAME(const Napi::CallbackInfo &info)
#define ACCESSOR_RW(CLASS, NAME) \
InstanceAccessor(#NAME, &CLASS::NAME ## Getter, &CLASS::NAME ## Setter)
#define ACCESSOR_R(CLASS, NAME) \
InstanceAccessor(#NAME, &CLASS::NAME ## Getter, nullptr)
#define ACCESSOR_M(CLASS, NAME) \
InstanceMethod(#NAME, &CLASS::NAME)
#define THIS_OBJ(VAR) \
Napi::Object VAR = info.This().As<Napi::Object>();
#define SETTER_STR_ARG \
SETTER_CHECK(IsString(), "String"); \
std::string v = value.ToString().Utf8Value();
#define SETTER_INT32_ARG \
SETTER_CHECK(IsNumber(), "Int32"); \
int v = value.ToNumber().Int32Value();
#define SETTER_INT_ARG SETTER_INT32_ARG
#define SETTER_BOOL_ARG \
SETTER_CHECK(IsBoolean(), "Bool"); \
bool v = value.ToBoolean().Value();
#define SETTER_UINT32_ARG \
SETTER_CHECK(IsNumber(), "Uint32"); \
unsigned int v = value.ToNumber().Uint32Value();
#define SETTER_UINT_ARG SETTER_UINT32_ARG
#define SETTER_OFFS_ARG \
SETTER_CHECK(IsNumber(), "Number"); \
size_t v = static_cast<size_t>(value.ToNumber().DoubleValue());
#define SETTER_DOUBLE_ARG \
SETTER_CHECK(IsNumber(), "Number"); \
double v = value.ToNumber().DoubleValue();
#define SETTER_FLOAT_ARG \
SETTER_CHECK(IsNumber(), "Number"); \
float v = value.ToNumber().FloatValue();
#define SETTER_EXT_ARG \
SETTER_CHECK(IsExternal(), "Pointer"); \
Napi::External v = value.As<Napi::External>();
#define SETTER_FUN_ARG \
SETTER_CHECK(IsFunction(), "Function"); \
Napi::Function v = value.As<Napi::Function>()
#define SETTER_OBJ_ARG \
SETTER_CHECK(IsObject(), "Object"); \
Napi::Object v = value.As<Napi::Object>()
#define SETTER_ARRV_ARG \
SETTER_CHECK(IsArrayBuffer(), "TypedArray"); \
Napi::ArrayBuffer v = value.As<Napi::ArrayBuffer>();
#define GET_AND_THROW_LAST_ERROR() \
do { \
const napi_extended_error_info *error_info; \
napi_get_last_error_info((env), &error_info); \
bool is_pending; \
napi_is_exception_pending((env), &is_pending); \
/* If an exception is already pending, don't rethrow it */ \
if (!is_pending) { \
const char* error_message = error_info->error_message != NULL \
? error_info->error_message \
: "empty error message"; \
JS_THROW(error_message); \
} \
} while (0)
#define NAPI_CALL(the_call) \
do { \
if ((the_call) != napi_ok) { \
GET_AND_THROW_LAST_ERROR(); \
RET_UNDEFINED; \
} \
} while (0)
#define JS_RUN(code, VAR) \
napi_value __RESULT_ ## VAR; \
NAPI_CALL( \
napi_run_script(env, napi_value(JS_STR(code)), &__RESULT_ ## VAR) \
); \
Napi::Value VAR(env, __RESULT_ ## VAR);
template<typename Type = uint8_t>
inline Type* getArrayData(
Napi::Env env,
Napi::Object obj,
int *num = nullptr
) {
Type *out = nullptr;
if (obj.IsTypedArray()) {
Napi::TypedArray ta = obj.As<Napi::TypedArray>();
size_t offset = ta.ByteOffset();
Napi::ArrayBuffer arr = ta.ArrayBuffer();
if (num) {
*num = ta.ByteLength() / sizeof(Type);
}
uint8_t *base = reinterpret_cast<uint8_t *>(arr.Data());
out = reinterpret_cast<Type *>(base + offset);
} else if (obj.IsArrayBuffer()) {
Napi::ArrayBuffer arr = obj.As<Napi::ArrayBuffer>();
if (num) {
*num = arr.ByteLength() / sizeof(Type);
}
out = reinterpret_cast<Type *>(arr.Data());
} else {
if (num) {
*num = 0;
}
JS_THROW("Argument must be of type `TypedArray`.");
}
return out;
}
template<typename Type = uint8_t>
inline Type* getBufferData(
Napi::Env env,
Napi::Object obj,
int *num = nullptr
) {
Type *out = nullptr;
if (num) {
*num = 0;
}
if (!obj.IsBuffer()) {
JS_THROW("Argument must be of type `Buffer`.");
return out;
}
Napi::Buffer<uint8_t> arr = obj.As< Napi::Buffer<uint8_t> >();
if (num) {
*num = arr.Length() / sizeof(Type);
}
out = arr.Data();
return out;
}
inline void *getData(Napi::Env env, Napi::Object obj) {
void *out = nullptr;
if (obj.IsTypedArray() || obj.IsArrayBuffer()) {
out = getArrayData<uint8_t>(env, obj);
} else if (obj.IsBuffer()) {
out = getBufferData<uint8_t>(env, obj);
} else if (obj.Has("data")) {
Napi::Object data = obj.Get("data").As<Napi::Object>();
if (data.IsTypedArray() || data.IsArrayBuffer()) {
out = getArrayData<uint8_t>(env, data);
} else if (data.IsBuffer()) {
out = getBufferData<uint8_t>(env, data);
}
}
return out;
}
inline Napi::Value consoleLog(
Napi::Env env,
int argc,
const Napi::Value *argv
) {
JS_RUN("console.log", log);
std::vector<napi_value> args;
for (int i = 0; i < argc; i++) {
args.push_back(napi_value(argv[i]));
}
log.As<Napi::Function>().Call(args);
RET_UNDEFINED;
}
inline Napi::Value consoleLog(Napi::Env env, const std::string &message) {
Napi::Value arg = JS_STR(message);
consoleLog(env, 1, &arg);
RET_UNDEFINED;
}
inline void eventEmit(
Napi::Object that,
const std::string &name,
int argc = 0,
const Napi::Value *argv = nullptr,
napi_async_context context = nullptr
) {
if (!that.Has("emit")) {
return;
}
Napi::Env env = that.Env();
Napi::String eventName = JS_STR(name);
Napi::Function thatEmit = that.Get("emit").As<Napi::Function>();
std::vector<napi_value> args;
args.push_back(napi_value(eventName));
for (int i = 0; i < argc; i++) {
args.push_back(napi_value(argv[i]));
}
if (context) {
thatEmit.MakeCallback(that, args, context);
} else {
thatEmit.Call(that, args);
}
}
typedef Napi::Value (*Es5MethodCallback)(const Napi::CallbackInfo& info);
typedef Napi::Value (*Es5GetterCallback)(const Napi::CallbackInfo& info);
typedef void (*Es5SetterCallback)(const Napi::CallbackInfo& info);
#define DECLARE_ES5_CLASS(CLASS, NAME) \
public: \
inline static CLASS *unwrap(Napi::Object thatObj) { \
CLASS *that; \
napi_status ns = napi_unwrap( \
thatObj.Env(), \
thatObj.Get(_nameEs5), \
reinterpret_cast<void**>(&that) \
); \
if (ns != napi_ok) { \
return nullptr; \
} \
return that; \
} \
private: \
static Napi::FunctionReference _ctorEs5; \
static const char *_nameEs5; \
static void _finalizeEs5(napi_env e, void *dest, void* hint); \
static napi_value _createEs5(napi_env e, napi_callback_info i); \
inline void super( \
const Napi::CallbackInfo& info, \
int argc, \
const Napi::Value *argv \
) { \
Napi::Function ctor = _ctorEs5.Value(); \
if (ctor.Has("super_")) { \
Napi::Function _super = ctor.Get("super_").As<Napi::Function>(); \
std::vector<napi_value> args; \
for (int i = 0; i < argc; i++) { \
args.push_back(argv[i]); \
} \
_super.Call(info.This(), args); \
} \
} \
inline void super( \
const Napi::CallbackInfo& info, \
int argc = 0, \
const napi_value *argv = nullptr \
) { \
Napi::Function ctor = _ctorEs5.Value(); \
if (ctor.Has("super_")) { \
Napi::Function _super = ctor.Get("super_").As<Napi::Function>(); \
_super.Call(info.This(), argc, argv); \
} \
} \
inline static Napi::Function wrap(Napi::Env env) { \
napi_value __initResult; \
napi_create_function( \
env, #NAME, 0, _createEs5, nullptr, &__initResult \
); \
Napi::Function ctor = Napi::Function(env, __initResult); \
_ctorEs5 = Napi::Persistent(ctor); \
_ctorEs5.SuppressDestruct(); \
return ctor; \
} \
inline static void method( \
const char *name, \
Es5MethodCallback cb \
) { \
Napi::Function proto = ( \
_ctorEs5.Value().Get("prototype").As<Napi::Function>() \
); \
proto.DefineProperty( \
Napi::PropertyDescriptor::Function( \
proto.Env(), proto, name, cb \
) \
); \
} \
inline static void accessorR( \
const char *name, \
Es5GetterCallback getter \
) { \
Napi::Function proto = ( \
_ctorEs5.Value().Get("prototype").As<Napi::Function>() \
); \
proto.DefineProperty( \
Napi::PropertyDescriptor::Accessor( \
proto.Env(), proto, name, getter \
) \
); \
} \
inline static void accessorRw( \
const char *name, \
Es5GetterCallback getter, \
Es5SetterCallback setter \
) { \
Napi::Function proto = ( \
_ctorEs5.Value().Get("prototype").As<Napi::Function>() \
); \
proto.DefineProperty( \
Napi::PropertyDescriptor::Accessor( \
proto.Env(), \
proto, \
name, \
getter, \
setter \
) \
); \
}
#define JS_GET_THAT(CLASS) \
CLASS *that = CLASS::unwrap(info.This().As<Napi::Object>());
#define JS_DECLARE_METHOD(CLASS, NAME) \
inline static JS_METHOD(__st_##NAME) { \
JS_GET_THAT(CLASS); \
return that->__i_##NAME(info); \
}; \
JS_METHOD(__i_##NAME);
#define JS_DECLARE_GETTER(CLASS, NAME) JS_DECLARE_METHOD(CLASS, NAME##Getter)
#define JS_DECLARE_SETTER(CLASS, NAME) \
inline static void __st_##NAME##Setter( \
const Napi::CallbackInfo &info \
) { \
JS_GET_THAT(CLASS); \
that->__i_##NAME##Setter(info, info[0]); \
} \
Napi::Value __i_##NAME##Setter( \
const Napi::CallbackInfo &info, \
const Napi::Value &value \
);
#define JS_IMPLEMENT_METHOD(CLASS, NAME) \
JS_METHOD(CLASS::__i_##NAME)
#define JS_IMPLEMENT_GETTER(CLASS, NAME) \
JS_IMPLEMENT_METHOD(CLASS, NAME##Getter)
#define JS_IMPLEMENT_SETTER(CLASS, NAME) \
Napi::Value CLASS::__i_##NAME##Setter( \
const Napi::CallbackInfo &info, \
const Napi::Value &value \
)
#define JS_ASSIGN_METHOD(NAME) method(#NAME, __st_##NAME)
#define JS_ASSIGN_GETTER(NAME) accessorR(#NAME, __st_##NAME##Getter)
#define JS_ASSIGN_SETTER(NAME) \
accessorRw(#NAME, __st_##NAME##Getter, __st_##NAME##Setter)
#define IMPLEMENT_ES5_CLASS(CLASS) \
Napi::FunctionReference CLASS::_ctorEs5; \
const char *CLASS::_nameEs5 = #CLASS; \
void CLASS::_finalizeEs5(napi_env e, void *dest, void* hint) { \
CLASS *instance = reinterpret_cast<CLASS*>(dest); \
delete instance; \
} \
napi_value CLASS::_createEs5(napi_env env, napi_callback_info i) { \
Napi::CallbackInfo info(env, i); \
CLASS *instance = new CLASS(info); \
Napi::Object wrapObj = Napi::Object::New(env); \
info.This().As<Napi::Object>().Set(_nameEs5, wrapObj); \
napi_wrap(env, wrapObj, instance, _finalizeEs5, nullptr, nullptr); \
return info.Env().Undefined(); \
}
#endif // ADDON_TOOLS_HPP