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()
|
||||
|
||||
if(ENABLE_SCRIPTING)
|
||||
add_subdirectory(src/script)
|
||||
list(APPEND ENABLES SCRIPTING)
|
||||
|
||||
if(BUILD_PYTHON)
|
||||
|
@ -774,6 +775,11 @@ if(USE_DEBUGGERS)
|
|||
list(APPEND FEATURES DEBUGGERS)
|
||||
endif()
|
||||
|
||||
if(ENABLE_SCRIPTING)
|
||||
list(APPEND FEATURE_SRC ${SCRIPT_SRC})
|
||||
list(APPEND TEST_SRC ${SCRIPT_TEST_SRC})
|
||||
endif()
|
||||
|
||||
foreach(FEATURE IN LISTS FEATURES)
|
||||
list(APPEND FEATURE_DEFINES "USE_${FEATURE}")
|
||||
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
|
|
@ -28,4 +28,4 @@ source_group("mCore" FILES ${SOURCE_FILES})
|
|||
source_group("mCore tests" FILES ${TEST_FILES})
|
||||
|
||||
export_directory(CORE SOURCE_FILES)
|
||||
export_directory(CORE_TEST TEST_FILES)
|
||||
export_directory(CORE_TEST TEST_FILES)
|
||||
|
|
|
@ -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