mirror of https://github.com/mgba-emu/mgba.git
Scripting: Initial runtime bringup work
This commit is contained in:
parent
7b4850024a
commit
1a33a71771
|
@ -733,6 +733,7 @@ if (USE_DISCORD_RPC)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(ENABLE_SCRIPTING)
|
if(ENABLE_SCRIPTING)
|
||||||
|
add_subdirectory(src/script)
|
||||||
list(APPEND ENABLES SCRIPTING)
|
list(APPEND ENABLES SCRIPTING)
|
||||||
|
|
||||||
if(BUILD_PYTHON)
|
if(BUILD_PYTHON)
|
||||||
|
@ -774,6 +775,11 @@ if(USE_DEBUGGERS)
|
||||||
list(APPEND FEATURES DEBUGGERS)
|
list(APPEND FEATURES DEBUGGERS)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_SCRIPTING)
|
||||||
|
list(APPEND FEATURE_SRC ${SCRIPT_SRC})
|
||||||
|
list(APPEND TEST_SRC ${SCRIPT_TEST_SRC})
|
||||||
|
endif()
|
||||||
|
|
||||||
foreach(FEATURE IN LISTS FEATURES)
|
foreach(FEATURE IN LISTS FEATURES)
|
||||||
list(APPEND FEATURE_DEFINES "USE_${FEATURE}")
|
list(APPEND FEATURE_DEFINES "USE_${FEATURE}")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
|
@ -0,0 +1,250 @@
|
||||||
|
/* Copyright (c) 2013-2021 Jeffrey Pfau
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
#ifndef M_SCRIPT_TYPES_H
|
||||||
|
#define M_SCRIPT_TYPES_H
|
||||||
|
|
||||||
|
#include <mgba-util/common.h>
|
||||||
|
|
||||||
|
CXX_GUARD_START
|
||||||
|
|
||||||
|
#include <mgba-util/vector.h>
|
||||||
|
|
||||||
|
#define _mAPPLY(...) __VA_ARGS__
|
||||||
|
|
||||||
|
#define mSCRIPT_VALUE_UNREF -1
|
||||||
|
#define mSCRIPT_PARAMS_MAX 8
|
||||||
|
|
||||||
|
#define mSCRIPT_TYPE_C_S32 int32_t
|
||||||
|
#define mSCRIPT_TYPE_C_U32 uint32_t
|
||||||
|
#define mSCRIPT_TYPE_C_F32 float
|
||||||
|
#define mSCRIPT_TYPE_C_PTR void*
|
||||||
|
#define mSCRIPT_TYPE_C_TABLE Table*
|
||||||
|
#define mSCRIPT_TYPE_C_S(STRUCT) struct STRUCT*
|
||||||
|
|
||||||
|
#define mSCRIPT_TYPE_FIELD_S32 s32
|
||||||
|
#define mSCRIPT_TYPE_FIELD_U32 u32
|
||||||
|
#define mSCRIPT_TYPE_FIELD_F32 f32
|
||||||
|
#define mSCRIPT_TYPE_FIELD_PTR opaque
|
||||||
|
#define mSCRIPT_TYPE_FIELD_TABLE opaque
|
||||||
|
#define mSCRIPT_TYPE_FIELD_S(STRUCT) opaque
|
||||||
|
|
||||||
|
#define mSCRIPT_TYPE_MS_S32 (&mSTSInt32)
|
||||||
|
#define mSCRIPT_TYPE_MS_U32 (&mSTUInt32)
|
||||||
|
#define mSCRIPT_TYPE_MS_F32 (&mSTFloat32)
|
||||||
|
#define mSCRIPT_TYPE_MS_TABLE (&mSTTable)
|
||||||
|
#define mSCRIPT_TYPE_MS_S(STRUCT) (&mSTStruct_ ## STRUCT)
|
||||||
|
|
||||||
|
#define _mSCRIPT_FIELD_NAME(V) (V)->name
|
||||||
|
|
||||||
|
#define mSCRIPT_TYPE_CMP_GENERIC(TYPE0, TYPE1) (TYPE0 == TYPE1)
|
||||||
|
#define mSCRIPT_TYPE_CMP_U32(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_U32, TYPE)
|
||||||
|
#define mSCRIPT_TYPE_CMP_S32(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_S32, TYPE)
|
||||||
|
#define mSCRIPT_TYPE_CMP_F32(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_F32, TYPE)
|
||||||
|
#define mSCRIPT_TYPE_CMP_PTR(TYPE) ((TYPE)->base >= mSCRIPT_TYPE_OPAQUE)
|
||||||
|
#define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME
|
||||||
|
#define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) _mAPPLY(mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1))
|
||||||
|
|
||||||
|
#define mSCRIPT_POP(STACK, TYPE, NAME) \
|
||||||
|
_mAPPLY(mSCRIPT_TYPE_C_ ## TYPE) NAME; \
|
||||||
|
do { \
|
||||||
|
struct mScriptValue* _val = mScriptListGetPointer(STACK, mScriptListSize(STACK) - 1); \
|
||||||
|
if (!(mSCRIPT_TYPE_CMP(TYPE, _val->type))) { \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
NAME = _val->value. _mAPPLY(mSCRIPT_TYPE_FIELD_ ## TYPE); \
|
||||||
|
mScriptListResize(STACK, -1); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define mSCRIPT_POP_0(...)
|
||||||
|
#define mSCRIPT_POP_1(FRAME, T0) mSCRIPT_POP(FRAME, T0, p0)
|
||||||
|
#define mSCRIPT_POP_2(FRAME, T0, T1) mSCRIPT_POP(FRAME, T1, p1); mSCRIPT_POP_1(FRAME, T0)
|
||||||
|
#define mSCRIPT_POP_3(FRAME, T0, T1, T2) mSCRIPT_POP(FRAME, T2, p2); mSCRIPT_POP_2(FRAME, T0, T1)
|
||||||
|
#define mSCRIPT_POP_4(FRAME, T0, T1, T2, T3) mSCRIPT_POP(FRAME, T3, p3); mSCRIPT_POP_3(FRAME, T0, T1, T2)
|
||||||
|
#define mSCRIPT_POP_5(FRAME, T0, T1, T2, T3, T4) mSCRIPT_POP(FRAME, T4, p4); mSCRIPT_POP_4(FRAME, T0, T1, T2, T3)
|
||||||
|
#define mSCRIPT_POP_6(FRAME, T0, T1, T2, T3, T4, T5) mSCRIPT_POP(FRAME, T5, p5); mSCRIPT_POP_5(FRAME, T0, T1, T2, T3, T4)
|
||||||
|
#define mSCRIPT_POP_7(FRAME, T0, T1, T2, T3, T4, T5, T6) mSCRIPT_POP(FRAME, T6, p6); mSCRIPT_POP_6(FRAME, T0, T1, T2, T3, T4, T5)
|
||||||
|
#define mSCRIPT_POP_8(FRAME, T0, T1, T2, T3, T4, T5, T6, T7) mSCRIPT_POP(FRAME, T7, p7); mSCRIPT_POP_7(FRAME, T0, T1, T2, T3, T4, T5, T6)
|
||||||
|
|
||||||
|
#define mSCRIPT_PUSH(STACK, TYPE, NAME) \
|
||||||
|
do { \
|
||||||
|
struct mScriptValue* _val = mScriptListAppend(STACK); \
|
||||||
|
_val->type = _mAPPLY(mSCRIPT_TYPE_MS_ ## TYPE); \
|
||||||
|
_val->refs = mSCRIPT_VALUE_UNREF; \
|
||||||
|
_val->value._mAPPLY(mSCRIPT_TYPE_FIELD_ ## TYPE) = NAME; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define mSCRIPT_ARG_NAMES_0
|
||||||
|
#define mSCRIPT_ARG_NAMES_1 p0
|
||||||
|
#define mSCRIPT_ARG_NAMES_2 p0, p1
|
||||||
|
#define mSCRIPT_ARG_NAMES_3 p0, p1, p2
|
||||||
|
#define mSCRIPT_ARG_NAMES_4 p0, p1, p2, p3
|
||||||
|
#define mSCRIPT_ARG_NAMES_5 p0, p1, p2, p3, p4
|
||||||
|
#define mSCRIPT_ARG_NAMES_6 p0, p1, p2, p3, p4, p5
|
||||||
|
#define mSCRIPT_ARG_NAMES_7 p0, p1, p2, p3, p4, p5, p6
|
||||||
|
#define mSCRIPT_ARG_NAMES_8 p0, p1, p2, p3, p4, p5, p6, p7
|
||||||
|
|
||||||
|
#define mSCRIPT_PREFIX_0(PREFIX, ...)
|
||||||
|
#define mSCRIPT_PREFIX_1(PREFIX, T0) PREFIX ## T0
|
||||||
|
#define mSCRIPT_PREFIX_2(PREFIX, T0, T1) PREFIX ## T0, PREFIX ## T1
|
||||||
|
#define mSCRIPT_PREFIX_3(PREFIX, T0, T1, T2) PREFIX ## T0, PREFIX ## T1, PREFIX ## T2
|
||||||
|
#define mSCRIPT_PREFIX_4(PREFIX, T0, T1, T2, T3) PREFIX ## T0, PREFIX ## T1, PREFIX ## T2, PREFIX ## T3
|
||||||
|
#define mSCRIPT_PREFIX_5(PREFIX, T0, T1, T2, T3, T4) PREFIX ## T0, PREFIX ## T1, PREFIX ## T2, PREFIX ## T3, PREFIX ## T4
|
||||||
|
#define mSCRIPT_PREFIX_6(PREFIX, T0, T1, T2, T3, T4, T5) PREFIX ## T0, PREFIX ## T1, PREFIX ## T2, PREFIX ## T3, PREFIX ## T4, PREFIX ## T5
|
||||||
|
#define mSCRIPT_PREFIX_7(PREFIX, T0, T1, T2, T3, T4, T5, T6) PREFIX ## T0, PREFIX ## T1, PREFIX ## T2, PREFIX ## T3, PREFIX ## T4, PREFIX ## T5, PREFIX ## T6
|
||||||
|
#define mSCRIPT_PREFIX_8(PREFIX, T0, T1, T2, T3, T4, T5, T6, T7) PREFIX ## T0, PREFIX ## T1, PREFIX ## T2, PREFIX ## T3, PREFIX ## T4, PREFIX ## T5, PREFIX ## T6, PREFIX ## T7
|
||||||
|
|
||||||
|
#define _mSCRIPT_CALL_VOID(FUNCTION, NPARAMS) FUNCTION(mSCRIPT_ARG_NAMES_ ## NPARAMS)
|
||||||
|
#define _mSCRIPT_CALL(RETURN, FUNCTION, NPARAMS) \
|
||||||
|
_mAPPLY(mSCRIPT_TYPE_C_ ## RETURN) out = FUNCTION(mSCRIPT_ARG_NAMES_ ## NPARAMS); \
|
||||||
|
mSCRIPT_PUSH(&frame->returnValues, RETURN, out)
|
||||||
|
|
||||||
|
#define mSCRIPT_EXPORT_STRUCT(STRUCT) \
|
||||||
|
const struct mScriptType mSTStruct_ ## STRUCT = { \
|
||||||
|
.base = mSCRIPT_TYPE_OBJECT, \
|
||||||
|
.size = sizeof(struct STRUCT), \
|
||||||
|
.name = "struct::" #STRUCT, \
|
||||||
|
.alloc = NULL, \
|
||||||
|
.free = NULL, \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define mSCRIPT_BIND_FUNCTION(NAME, RETURN, FUNCTION, NPARAMS, ...) \
|
||||||
|
static bool _binding_ ## NAME(struct mScriptFrame* frame) { \
|
||||||
|
mSCRIPT_POP_ ## NPARAMS(&frame->arguments, __VA_ARGS__); \
|
||||||
|
if (mScriptListSize(&frame->arguments)) { \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
_mSCRIPT_CALL(RETURN, FUNCTION, NPARAMS); \
|
||||||
|
return true; \
|
||||||
|
} \
|
||||||
|
const struct mScriptFunction NAME = { \
|
||||||
|
.signature = { \
|
||||||
|
.parameters = { \
|
||||||
|
.count = NPARAMS, \
|
||||||
|
.entries = { _mAPPLY(mSCRIPT_PREFIX_ ## NPARAMS(mSCRIPT_TYPE_MS_, __VA_ARGS__)) } \
|
||||||
|
}, \
|
||||||
|
.returnType = { \
|
||||||
|
.count = 1, \
|
||||||
|
.entries = { mSCRIPT_TYPE_MS_ ## RETURN } \
|
||||||
|
}, \
|
||||||
|
}, \
|
||||||
|
.call = _binding_ ## NAME \
|
||||||
|
};
|
||||||
|
|
||||||
|
#define mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, NPARAMS, ...) \
|
||||||
|
static bool _binding_ ## NAME(struct mScriptFrame* frame) { \
|
||||||
|
mSCRIPT_POP_ ## NPARAMS(&frame->arguments, __VA_ARGS__); \
|
||||||
|
if (mScriptListSize(&frame->arguments)) { \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
_mSCRIPT_CALL_VOID(FUNCTION, NPARAMS); \
|
||||||
|
return true; \
|
||||||
|
} \
|
||||||
|
const struct mScriptFunction NAME = { \
|
||||||
|
.signature = { \
|
||||||
|
.parameters = { \
|
||||||
|
.count = NPARAMS, \
|
||||||
|
.entries = { _mAPPLY(mSCRIPT_PREFIX_ ## NPARAMS(mSCRIPT_TYPE_MS_, __VA_ARGS__)) } \
|
||||||
|
}, \
|
||||||
|
.returnType = { \
|
||||||
|
.count = 0, \
|
||||||
|
}, \
|
||||||
|
}, \
|
||||||
|
.call = _binding_ ## NAME \
|
||||||
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
mSCRIPT_TYPE_VOID = 0,
|
||||||
|
mSCRIPT_TYPE_SINT,
|
||||||
|
mSCRIPT_TYPE_UINT,
|
||||||
|
mSCRIPT_TYPE_FLOAT,
|
||||||
|
mSCRIPT_TYPE_STRING,
|
||||||
|
mSCRIPT_TYPE_FUNCTION,
|
||||||
|
mSCRIPT_TYPE_OPAQUE,
|
||||||
|
mSCRIPT_TYPE_OBJECT,
|
||||||
|
mSCRIPT_TYPE_TUPLE,
|
||||||
|
mSCRIPT_TYPE_LIST,
|
||||||
|
mSCRIPT_TYPE_TABLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Table;
|
||||||
|
struct mScriptType;
|
||||||
|
extern const struct mScriptType mSTVoid;
|
||||||
|
extern const struct mScriptType mSTSInt32;
|
||||||
|
extern const struct mScriptType mSTUInt32;
|
||||||
|
extern const struct mScriptType mSTFloat32;
|
||||||
|
extern const struct mScriptType mSTTable;
|
||||||
|
|
||||||
|
struct mScriptTypeTuple {
|
||||||
|
size_t count;
|
||||||
|
const struct mScriptType* entries[mSCRIPT_PARAMS_MAX];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mScriptTypeFunction {
|
||||||
|
struct mScriptTypeTuple parameters;
|
||||||
|
struct mScriptTypeTuple returnType;
|
||||||
|
// TODO: varargs, kwargs, defaults
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mScriptValue;
|
||||||
|
struct mScriptType {
|
||||||
|
int base;
|
||||||
|
size_t size;
|
||||||
|
const char* name;
|
||||||
|
void (*alloc)(const struct mScriptType*, struct mScriptValue*);
|
||||||
|
void (*free)(const struct mScriptType*, struct mScriptValue*);
|
||||||
|
uint32_t (*hash)(const struct mScriptType*, struct mScriptValue*);
|
||||||
|
union {
|
||||||
|
struct mScriptTypeTuple tuple;
|
||||||
|
struct mScriptTypeFunction function;
|
||||||
|
void* opaque;
|
||||||
|
} details;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mScriptValue {
|
||||||
|
const struct mScriptType* type;
|
||||||
|
int refs;
|
||||||
|
union {
|
||||||
|
int32_t s32;
|
||||||
|
uint32_t u32;
|
||||||
|
float f32;
|
||||||
|
void* opaque;
|
||||||
|
} value;
|
||||||
|
};
|
||||||
|
|
||||||
|
DECLARE_VECTOR(mScriptList, struct mScriptValue)
|
||||||
|
|
||||||
|
struct mScriptFrame {
|
||||||
|
struct mScriptList arguments;
|
||||||
|
struct mScriptList returnValues;
|
||||||
|
// TODO: Exception/failure codes
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mScriptFunction {
|
||||||
|
struct mScriptTypeFunction signature;
|
||||||
|
bool (*call)(struct mScriptFrame*);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mScriptValue* mScriptValueAlloc(const struct mScriptType* type);
|
||||||
|
void mScriptValueRef(struct mScriptValue* val);
|
||||||
|
void mScriptValueDeref(struct mScriptValue* val);
|
||||||
|
|
||||||
|
bool mScriptTableInsert(struct mScriptValue* table, struct mScriptValue* key, struct mScriptValue* value);
|
||||||
|
bool mScriptTableRemove(struct mScriptValue* table, struct mScriptValue* key);
|
||||||
|
struct mScriptValue* mScriptTableLookup(struct mScriptValue* table, struct mScriptValue* key);
|
||||||
|
|
||||||
|
void mScriptFrameInit(struct mScriptFrame* frame);
|
||||||
|
void mScriptFrameDeinit(struct mScriptFrame* frame);
|
||||||
|
|
||||||
|
bool mScriptPopS32(struct mScriptList* list, int32_t* out);
|
||||||
|
bool mScriptPopU32(struct mScriptList* list, uint32_t* out);
|
||||||
|
bool mScriptPopF32(struct mScriptList* list, float* out);
|
||||||
|
bool mScriptPopPointer(struct mScriptList* list, void** out);
|
||||||
|
|
||||||
|
bool mScriptCast(const struct mScriptType* type, const struct mScriptValue* input, struct mScriptValue* output);
|
||||||
|
bool mScriptCoerceFrame(const struct mScriptTypeTuple* types, struct mScriptList* frame);
|
||||||
|
bool mScriptInvoke(const struct mScriptFunction* fn, struct mScriptFrame* frame);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,12 @@
|
||||||
|
include(ExportDirectory)
|
||||||
|
set(SOURCE_FILES
|
||||||
|
types.c)
|
||||||
|
|
||||||
|
set(TEST_FILES
|
||||||
|
test/types.c)
|
||||||
|
|
||||||
|
source_group("Scripting" FILES ${SOURCE_FILES})
|
||||||
|
source_group("Scripting tests" FILES ${TEST_FILES})
|
||||||
|
|
||||||
|
export_directory(SCRIPT SOURCE_FILES)
|
||||||
|
export_directory(SCRIPT_TEST TEST_FILES)
|
|
@ -0,0 +1,229 @@
|
||||||
|
/* Copyright (c) 2013-2021 Jeffrey Pfau
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
#include "util/test/suite.h"
|
||||||
|
|
||||||
|
#include <mgba/script/types.h>
|
||||||
|
|
||||||
|
struct Test {
|
||||||
|
int32_t a;
|
||||||
|
};
|
||||||
|
|
||||||
|
mSCRIPT_EXPORT_STRUCT(Test);
|
||||||
|
|
||||||
|
static int voidOne(void) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void discard(int ignored) {
|
||||||
|
UNUSED(ignored);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int identityInt(int in) {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float identityFloat(float in) {
|
||||||
|
return in;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct Test* identityStruct(struct Test* t) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int addInts(int a, int b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int subInts(int a, int b) {
|
||||||
|
return a - b;
|
||||||
|
}
|
||||||
|
|
||||||
|
mSCRIPT_BIND_FUNCTION(boundVoidOne, S32, voidOne, 0);
|
||||||
|
mSCRIPT_BIND_VOID_FUNCTION(boundDiscard, discard, 1, S32);
|
||||||
|
mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32);
|
||||||
|
mSCRIPT_BIND_FUNCTION(boundIdentityFloat, F32, identityFloat, 1, F32);
|
||||||
|
mSCRIPT_BIND_FUNCTION(boundIdentityStruct, S(Test), identityStruct, 1, S(Test));
|
||||||
|
mSCRIPT_BIND_FUNCTION(boundAddInts, S32, addInts, 2, S32, S32);
|
||||||
|
mSCRIPT_BIND_FUNCTION(boundSubInts, S32, subInts, 2, S32, S32);
|
||||||
|
|
||||||
|
M_TEST_DEFINE(voidArgs) {
|
||||||
|
struct mScriptFrame frame;
|
||||||
|
mScriptFrameInit(&frame);
|
||||||
|
assert_true(mScriptInvoke(&boundVoidOne, &frame));
|
||||||
|
int32_t val;
|
||||||
|
assert_true(mScriptPopS32(&frame.returnValues, &val));
|
||||||
|
assert_int_equal(val, 1);
|
||||||
|
mScriptFrameDeinit(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(voidFunc) {
|
||||||
|
struct mScriptFrame frame;
|
||||||
|
mScriptFrameInit(&frame);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, S32, 1);
|
||||||
|
assert_true(mScriptInvoke(&boundDiscard, &frame));
|
||||||
|
mScriptFrameDeinit(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(identityFunctionS32) {
|
||||||
|
struct mScriptFrame frame;
|
||||||
|
mScriptFrameInit(&frame);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, S32, 1);
|
||||||
|
assert_true(mScriptInvoke(&boundIdentityInt, &frame));
|
||||||
|
int32_t val;
|
||||||
|
assert_true(mScriptPopS32(&frame.returnValues, &val));
|
||||||
|
assert_int_equal(val, 1);
|
||||||
|
mScriptFrameDeinit(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(identityFunctionF32) {
|
||||||
|
struct mScriptFrame frame;
|
||||||
|
mScriptFrameInit(&frame);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, F32, 3.125f);
|
||||||
|
assert_true(mScriptInvoke(&boundIdentityFloat, &frame));
|
||||||
|
float val;
|
||||||
|
assert_true(mScriptPopF32(&frame.returnValues, &val));
|
||||||
|
assert_float_equal(val, 3.125f, 0.f);
|
||||||
|
mScriptFrameDeinit(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(identityFunctionStruct) {
|
||||||
|
struct mScriptFrame frame;
|
||||||
|
struct Test v = {};
|
||||||
|
mScriptFrameInit(&frame);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, S(Test), &v);
|
||||||
|
assert_true(mScriptInvoke(&boundIdentityStruct, &frame));
|
||||||
|
struct Test* val;
|
||||||
|
assert_true(mScriptPopPointer(&frame.returnValues, (void**) &val));
|
||||||
|
assert_ptr_equal(val, &v);
|
||||||
|
mScriptFrameDeinit(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(addS32) {
|
||||||
|
struct mScriptFrame frame;
|
||||||
|
mScriptFrameInit(&frame);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, S32, 1);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, S32, 2);
|
||||||
|
assert_true(mScriptInvoke(&boundAddInts, &frame));
|
||||||
|
int32_t val;
|
||||||
|
assert_true(mScriptPopS32(&frame.returnValues, &val));
|
||||||
|
assert_int_equal(val, 3);
|
||||||
|
mScriptFrameDeinit(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(subS32) {
|
||||||
|
struct mScriptFrame frame;
|
||||||
|
mScriptFrameInit(&frame);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, S32, 2);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, S32, 1);
|
||||||
|
assert_true(mScriptInvoke(&boundSubInts, &frame));
|
||||||
|
int32_t val;
|
||||||
|
assert_true(mScriptPopS32(&frame.returnValues, &val));
|
||||||
|
assert_int_equal(val, 1);
|
||||||
|
mScriptFrameDeinit(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(wrongArgCountLo) {
|
||||||
|
struct mScriptFrame frame;
|
||||||
|
mScriptFrameInit(&frame);
|
||||||
|
assert_false(mScriptInvoke(&boundIdentityInt, &frame));
|
||||||
|
mScriptFrameDeinit(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(wrongArgCountHi) {
|
||||||
|
struct mScriptFrame frame;
|
||||||
|
mScriptFrameInit(&frame);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, S32, 1);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, S32, 1);
|
||||||
|
assert_false(mScriptInvoke(&boundIdentityInt, &frame));
|
||||||
|
mScriptFrameDeinit(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(wrongArgType) {
|
||||||
|
struct mScriptFrame frame;
|
||||||
|
mScriptFrameInit(&frame);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, S32, 1);
|
||||||
|
assert_false(mScriptInvoke(&boundIdentityStruct, &frame));
|
||||||
|
mScriptFrameDeinit(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(coerceToFloat) {
|
||||||
|
struct mScriptFrame frame;
|
||||||
|
mScriptFrameInit(&frame);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, S32, 1);
|
||||||
|
assert_true(mScriptInvoke(&boundIdentityFloat, &frame));
|
||||||
|
float val;
|
||||||
|
assert_true(mScriptPopF32(&frame.returnValues, &val));
|
||||||
|
assert_float_equal(val, 1.f, 0.f);
|
||||||
|
mScriptFrameDeinit(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(coerceFromFloat) {
|
||||||
|
struct mScriptFrame frame;
|
||||||
|
mScriptFrameInit(&frame);
|
||||||
|
mSCRIPT_PUSH(&frame.arguments, F32, 1.25f);
|
||||||
|
assert_true(mScriptInvoke(&boundIdentityInt, &frame));
|
||||||
|
int val;
|
||||||
|
assert_true(mScriptPopS32(&frame.returnValues, &val));
|
||||||
|
assert_int_equal(val, 1);
|
||||||
|
mScriptFrameDeinit(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_DEFINE(hashTableBasic) {
|
||||||
|
struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
|
||||||
|
assert_non_null(table);
|
||||||
|
|
||||||
|
struct mScriptValue* intValue = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32);
|
||||||
|
assert_ptr_equal(intValue->type, mSCRIPT_TYPE_MS_S32);
|
||||||
|
assert_int_equal(intValue->value.s32, 0);
|
||||||
|
assert_int_equal(intValue->refs, 1);
|
||||||
|
|
||||||
|
struct mScriptValue intKey = {
|
||||||
|
.type = mSCRIPT_TYPE_MS_S32,
|
||||||
|
.value = {
|
||||||
|
.s32 = 1234
|
||||||
|
},
|
||||||
|
.refs = mSCRIPT_VALUE_UNREF
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mScriptValue badKey = {
|
||||||
|
.type = mSCRIPT_TYPE_MS_S32,
|
||||||
|
.value = {
|
||||||
|
.s32 = 1235
|
||||||
|
},
|
||||||
|
.refs = mSCRIPT_VALUE_UNREF
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_true(mScriptTableInsert(table, &intKey, intValue));
|
||||||
|
assert_int_equal(intValue->refs, 2);
|
||||||
|
|
||||||
|
struct mScriptValue* lookupValue = mScriptTableLookup(table, &intKey);
|
||||||
|
assert_non_null(lookupValue);
|
||||||
|
assert_ptr_equal(lookupValue, intValue);
|
||||||
|
|
||||||
|
lookupValue = mScriptTableLookup(table, &badKey);
|
||||||
|
assert_null(lookupValue);
|
||||||
|
|
||||||
|
assert_true(mScriptTableRemove(table, &intKey));
|
||||||
|
assert_int_equal(intValue->refs, 1);
|
||||||
|
|
||||||
|
mScriptValueDeref(intValue);
|
||||||
|
mScriptValueDeref(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
M_TEST_SUITE_DEFINE(mScript,
|
||||||
|
cmocka_unit_test(voidArgs),
|
||||||
|
cmocka_unit_test(voidFunc),
|
||||||
|
cmocka_unit_test(identityFunctionS32),
|
||||||
|
cmocka_unit_test(identityFunctionF32),
|
||||||
|
cmocka_unit_test(identityFunctionStruct),
|
||||||
|
cmocka_unit_test(addS32),
|
||||||
|
cmocka_unit_test(subS32),
|
||||||
|
cmocka_unit_test(wrongArgCountLo),
|
||||||
|
cmocka_unit_test(wrongArgCountHi),
|
||||||
|
cmocka_unit_test(wrongArgType),
|
||||||
|
cmocka_unit_test(coerceToFloat),
|
||||||
|
cmocka_unit_test(coerceFromFloat),
|
||||||
|
cmocka_unit_test(hashTableBasic))
|
|
@ -0,0 +1,299 @@
|
||||||
|
/* Copyright (c) 2013-2021 Jeffrey Pfau
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
#include <mgba/script/types.h>
|
||||||
|
|
||||||
|
#include <mgba-util/table.h>
|
||||||
|
|
||||||
|
static void _allocTable(const struct mScriptType*, struct mScriptValue*);
|
||||||
|
static void _freeTable(const struct mScriptType*, struct mScriptValue*);
|
||||||
|
static void _deinitTableValue(void*);
|
||||||
|
|
||||||
|
static uint32_t _hashScalar(const struct mScriptType*, struct mScriptValue*);
|
||||||
|
|
||||||
|
const struct mScriptType mSTVoid = {
|
||||||
|
.base = mSCRIPT_TYPE_VOID,
|
||||||
|
.size = 0,
|
||||||
|
.name = "void",
|
||||||
|
.alloc = NULL,
|
||||||
|
.free = NULL,
|
||||||
|
.hash = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct mScriptType mSTSInt32 = {
|
||||||
|
.base = mSCRIPT_TYPE_SINT,
|
||||||
|
.size = 4,
|
||||||
|
.name = "s32",
|
||||||
|
.alloc = NULL,
|
||||||
|
.free = NULL,
|
||||||
|
.hash = _hashScalar,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct mScriptType mSTUInt32 = {
|
||||||
|
.base = mSCRIPT_TYPE_UINT,
|
||||||
|
.size = 4,
|
||||||
|
.name = "u32",
|
||||||
|
.alloc = NULL,
|
||||||
|
.free = NULL,
|
||||||
|
.hash = _hashScalar,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct mScriptType mSTFloat32 = {
|
||||||
|
.base = mSCRIPT_TYPE_FLOAT,
|
||||||
|
.size = 4,
|
||||||
|
.name = "f32",
|
||||||
|
.alloc = NULL,
|
||||||
|
.free = NULL,
|
||||||
|
.hash = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct mScriptType mSTTable = {
|
||||||
|
.base = mSCRIPT_TYPE_TABLE,
|
||||||
|
.size = sizeof(struct Table),
|
||||||
|
.name = "table",
|
||||||
|
.alloc = _allocTable,
|
||||||
|
.free = _freeTable,
|
||||||
|
.hash = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
DEFINE_VECTOR(mScriptList, struct mScriptValue)
|
||||||
|
|
||||||
|
void _allocTable(const struct mScriptType* type, struct mScriptValue* val) {
|
||||||
|
UNUSED(type);
|
||||||
|
val->value.opaque = malloc(sizeof(struct Table));
|
||||||
|
TableInit(val->value.opaque, 0, _deinitTableValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _freeTable(const struct mScriptType* type, struct mScriptValue* val) {
|
||||||
|
UNUSED(type);
|
||||||
|
TableDeinit(val->value.opaque);
|
||||||
|
free(val->value.opaque);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _deinitTableValue(void* val) {
|
||||||
|
mScriptValueDeref(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t _hashScalar(const struct mScriptType* type, struct mScriptValue* val) {
|
||||||
|
// From https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key
|
||||||
|
uint32_t x;
|
||||||
|
switch (type->base) {
|
||||||
|
case mSCRIPT_TYPE_SINT:
|
||||||
|
x = val->value.s32;
|
||||||
|
break;
|
||||||
|
case mSCRIPT_TYPE_UINT:
|
||||||
|
x = val->value.u32;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
x = ((x >> 16) ^ x) * 0x45D9F3B;
|
||||||
|
x = ((x >> 16) ^ x) * 0x45D9F3B;
|
||||||
|
x = (x >> 16) ^ x;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mScriptValue* mScriptValueAlloc(const struct mScriptType* type) {
|
||||||
|
// TODO: Use an arena instead of just the generic heap
|
||||||
|
struct mScriptValue* val = malloc(sizeof(*val));
|
||||||
|
val->refs = 1;
|
||||||
|
if (type->alloc) {
|
||||||
|
type->alloc(type, val);
|
||||||
|
} else {
|
||||||
|
val->value.opaque = NULL;
|
||||||
|
}
|
||||||
|
val->type = type;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mScriptValueRef(struct mScriptValue* val) {
|
||||||
|
if (val->refs == INT_MAX) {
|
||||||
|
abort();
|
||||||
|
} else if (val->refs == mSCRIPT_VALUE_UNREF) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
++val->refs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mScriptValueDeref(struct mScriptValue* val) {
|
||||||
|
--val->refs;
|
||||||
|
if (val->refs > 0) {
|
||||||
|
return;
|
||||||
|
} else if (val->refs < 0) {
|
||||||
|
val->refs = mSCRIPT_VALUE_UNREF;
|
||||||
|
}
|
||||||
|
if (val->type->free) {
|
||||||
|
val->type->free(val->type, val);
|
||||||
|
}
|
||||||
|
free(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mScriptTableInsert(struct mScriptValue* table, struct mScriptValue* key, struct mScriptValue* value) {
|
||||||
|
if (table->type != mSCRIPT_TYPE_MS_TABLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!key->type->hash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct Table* t = table->value.opaque;
|
||||||
|
uint32_t hash = key->type->hash(key->type, key);
|
||||||
|
mScriptValueRef(value);
|
||||||
|
TableInsert(t, hash, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mScriptTableRemove(struct mScriptValue* table, struct mScriptValue* key) {
|
||||||
|
if (table->type != mSCRIPT_TYPE_MS_TABLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!key->type->hash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct Table* t = table->value.opaque;
|
||||||
|
uint32_t hash = key->type->hash(key->type, key);
|
||||||
|
TableRemove(t, hash);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct mScriptValue* mScriptTableLookup(struct mScriptValue* table, struct mScriptValue* key) {
|
||||||
|
if (table->type != mSCRIPT_TYPE_MS_TABLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!key->type->hash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct Table* t = table->value.opaque;
|
||||||
|
uint32_t hash = key->type->hash(key->type, key);
|
||||||
|
return TableLookup(t, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mScriptFrameInit(struct mScriptFrame* frame) {
|
||||||
|
mScriptListInit(&frame->arguments, 4);
|
||||||
|
mScriptListInit(&frame->returnValues, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mScriptFrameDeinit(struct mScriptFrame* frame) {
|
||||||
|
mScriptListDeinit(&frame->returnValues);
|
||||||
|
mScriptListDeinit(&frame->arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mScriptPopS32(struct mScriptList* list, int32_t* out) {
|
||||||
|
mSCRIPT_POP(list, S32, val);
|
||||||
|
*out = val;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mScriptPopU32(struct mScriptList* list, uint32_t* out) {
|
||||||
|
mSCRIPT_POP(list, U32, val);
|
||||||
|
*out = val;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mScriptPopF32(struct mScriptList* list, float* out) {
|
||||||
|
mSCRIPT_POP(list, F32, val);
|
||||||
|
*out = val;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mScriptPopPointer(struct mScriptList* list, void** out) {
|
||||||
|
mSCRIPT_POP(list, PTR, val);
|
||||||
|
*out = val;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mScriptCast(const struct mScriptType* type, const struct mScriptValue* input, struct mScriptValue* output) {
|
||||||
|
switch (type->base) {
|
||||||
|
case mSCRIPT_TYPE_VOID:
|
||||||
|
return false;
|
||||||
|
case mSCRIPT_TYPE_SINT:
|
||||||
|
switch (input->type->base) {
|
||||||
|
case mSCRIPT_TYPE_SINT:
|
||||||
|
output->value.s32 = input->value.s32;
|
||||||
|
break;
|
||||||
|
case mSCRIPT_TYPE_UINT:
|
||||||
|
output->value.s32 = input->value.u32;
|
||||||
|
break;
|
||||||
|
case mSCRIPT_TYPE_FLOAT:
|
||||||
|
switch (input->type->size) {
|
||||||
|
case 4:
|
||||||
|
output->value.s32 = input->value.f32;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case mSCRIPT_TYPE_UINT:
|
||||||
|
switch (input->type->base) {
|
||||||
|
case mSCRIPT_TYPE_SINT:
|
||||||
|
output->value.u32 = input->value.s32;
|
||||||
|
break;
|
||||||
|
case mSCRIPT_TYPE_UINT:
|
||||||
|
output->value.u32 = input->value.u32;
|
||||||
|
break;
|
||||||
|
case mSCRIPT_TYPE_FLOAT:
|
||||||
|
switch (input->type->size) {
|
||||||
|
case 4:
|
||||||
|
output->value.u32 = input->value.f32;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case mSCRIPT_TYPE_FLOAT:
|
||||||
|
switch (input->type->base) {
|
||||||
|
case mSCRIPT_TYPE_SINT:
|
||||||
|
output->value.f32 = input->value.s32;
|
||||||
|
break;
|
||||||
|
case mSCRIPT_TYPE_UINT:
|
||||||
|
output->value.f32 = input->value.u32;
|
||||||
|
break;
|
||||||
|
case mSCRIPT_TYPE_FLOAT:
|
||||||
|
switch (input->type->size) {
|
||||||
|
case 4:
|
||||||
|
output->value.f32 = input->value.f32;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
output->type = type;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mScriptCoerceFrame(const struct mScriptTypeTuple* types, struct mScriptList* frame) {
|
||||||
|
if (types->count != mScriptListSize(frame)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < types->count; ++i) {
|
||||||
|
if (types->entries[i] == mScriptListGetPointer(frame, i)->type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!mScriptCast(types->entries[i], mScriptListGetPointer(frame, i), mScriptListGetPointer(frame, i))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mScriptInvoke(const struct mScriptFunction* fn, struct mScriptFrame* frame) {
|
||||||
|
if (!mScriptCoerceFrame(&fn->signature.parameters, &frame->arguments)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return fn->call(frame);
|
||||||
|
}
|
Loading…
Reference in New Issue