Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2022-05-30 17:44:36 -07:00
commit cda444b27f
65 changed files with 9558 additions and 80 deletions

View File

@ -40,6 +40,7 @@ Misc:
0.10.0: (Future)
Features:
- Preliminary Lua scripting support
- Presets for Game Boy palettes
- Add Super Game Boy palettes for original Game Boy games
- Tool for converting scanned pictures of e-Reader cards to raw dotcode data
@ -115,6 +116,7 @@ Misc:
- Qt: Show warning if XQ audio is toggled while loaded (fixes mgba.io/i/2295)
- Qt: Add e-Card passing to the command line (closes mgba.io/i/2474)
- Qt: Boot both a multiboot image and ROM with CLI args (closes mgba.io/i/1941)
- Qt: Improve cheat parsing (fixes mgba.io/i/2297)
- Windows: Attach to console if present
- Vita: Add bilinear filtering option (closes mgba.io/i/344)

View File

@ -56,6 +56,7 @@ if(NOT LIBMGBA_ONLY)
set(USE_LIBZIP ON CACHE BOOL "Whether or not to enable LIBZIP support")
set(USE_SQLITE3 ON CACHE BOOL "Whether or not to enable SQLite3 support")
set(USE_ELF ON CACHE BOOL "Whether or not to enable ELF support")
set(USE_LUA ON CACHE BOOL "Whether or not to enable Lua scripting support")
set(M_CORE_GBA ON CACHE BOOL "Build Game Boy Advance core")
set(M_CORE_GB ON CACHE BOOL "Build Game Boy core")
set(M_CORE_DS ON CACHE BOOL "Build DS core")
@ -81,6 +82,7 @@ if(NOT LIBMGBA_ONLY)
set(BUILD_GL ON CACHE BOOL "Build with OpenGL")
set(BUILD_GLES2 ON CACHE BOOL "Build with OpenGL|ES 2")
set(BUILD_GLES3 ON CACHE BOOL "Build with OpenGL|ES 3")
set(BUILD_DOCGEN OFF CACHE BOOL "Build the scripting API documentation generator")
set(USE_EPOXY ON CACHE STRING "Build with libepoxy")
set(DISABLE_DEPS OFF CACHE BOOL "Build without dependencies")
set(DISTBUILD OFF CACHE BOOL "Build distribution packages")
@ -88,6 +90,7 @@ if(NOT LIBMGBA_ONLY)
set(WIN32_UNIX_PATHS OFF CACHE BOOL "Use Unix-like paths")
mark_as_advanced(WIN32_UNIX_PATHS)
endif()
mark_as_advanced(BUILD_DOCGEN)
else()
set(DISABLE_FRONTENDS ON)
set(DISABLE_DEPS ON)
@ -751,6 +754,17 @@ endif()
if(ENABLE_SCRIPTING)
list(APPEND ENABLES SCRIPTING)
if(NOT USE_LUA VERSION_LESS 5.1)
find_feature(USE_LUA "Lua" ${USE_LUA})
else()
find_feature(USE_LUA "Lua")
endif()
if(USE_LUA)
list(APPEND FEATURE_DEFINES USE_LUA)
include_directories(AFTER ${LUA_INCLUDE_DIR})
list(APPEND FEATURE_DEFINES LUA_VERSION_ONLY=\"${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}\")
list(APPEND DEPENDENCY_LIB ${LUA_LIBRARY})
endif()
if(BUILD_PYTHON)
find_package(PythonLibs ${USE_PYTHON_VERSION})
@ -758,6 +772,7 @@ if(ENABLE_SCRIPTING)
include_directories(AFTER ${PYTHON_INCLUDE_DIRS})
list(APPEND ENABLES PYTHON)
endif()
add_subdirectory(src/script)
endif()
add_subdirectory(src/arm)
@ -801,6 +816,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()
@ -970,6 +990,11 @@ if(BUILD_UPDATER)
endif()
endif()
if(ENABLE_SCRIPTING AND BUILD_DOCGEN)
add_executable(docgen ${CMAKE_CURRENT_SOURCE_DIR}/src/script/docgen.c)
target_link_libraries(docgen ${OS_LIB} ${PLATFORM_LIBRARY} ${BINARY_NAME})
endif()
if(BUILD_SDL)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/sdl ${CMAKE_CURRENT_BINARY_DIR}/sdl)
endif()
@ -1248,6 +1273,14 @@ if(NOT QUIET AND NOT LIBMGBA_ONLY)
message(STATUS " ELF loading support: ${USE_ELF}")
message(STATUS " Discord Rich Presence support: ${USE_DISCORD_RPC}")
message(STATUS " OpenGL support: ${SUMMARY_GL}")
message(STATUS "Scripting support: ${ENABLE_SCRIPTING}")
if(ENABLE_SCRIPTING)
if(LUA_VERSION_STRING)
message(STATUS " Lua: ${LUA_VERSION_STRING}")
else()
message(STATUS " Lua: ${USE_LUA}")
endif()
endif()
message(STATUS "Frontends:")
message(STATUS " Qt: ${BUILD_QT}")
message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}")

View File

@ -23,6 +23,7 @@ Features
- Solar sensor support for Boktai games.
- Game Boy Camera and Game Boy Printer support.
- A built-in GBA BIOS implementation, and ability to load external BIOS files. DS currently requires BIOS and firmware dumps[<sup>[2]</sup>](#dscaveat).
- Scripting support using Lua.
- Turbo/fast-forward support by holding Tab.
- Rewind by holding Backquote.
- Frameskip, configurable up to 10.
@ -172,7 +173,7 @@ This will build and install medusa into `/usr/bin` and `/usr/lib`. Dependencies
If you are on macOS, the steps are a little different. Assuming you are using the homebrew package manager, the recommended commands to obtain the dependencies and build are:
brew install cmake ffmpeg libzip qt5 sdl2 libedit pkg-config
brew install cmake ffmpeg libzip qt5 sdl2 libedit lua pkg-config
mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` ..
@ -186,7 +187,7 @@ Note that you should not do a `make install` on macOS, as it will not work prope
To build on Windows for development, using MSYS2 is recommended. Follow the installation steps found on their [website](https://msys2.github.io). Make sure you're running the 32-bit version ("MSYS2 MinGW 32-bit") (or the 64-bit version "MSYS2 MinGW 64-bit" if you want to build for x86_64) and run this additional command (including the braces) to install the needed dependencies (please note that this involves downloading over 1100MiB of packages, so it will take a long time):
pacman -Sy --needed base-devel git ${MINGW_PACKAGE_PREFIX}-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,pkgconf,qt5,SDL2,ntldd-git}
pacman -Sy --needed base-devel git ${MINGW_PACKAGE_PREFIX}-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,lua,pkgconf,qt5,SDL2,ntldd-git}
Check out the source code by running this command:
@ -205,7 +206,7 @@ Please note that this build of medusa for Windows is not suitable for distributi
To build using Visual Studio is a similarly complicated setup. To begin you will need to install [vcpkg](https://github.com/Microsoft/vcpkg). After installing vcpkg you will need to install several additional packages:
vcpkg install ffmpeg[vpx,x264] libepoxy libpng libzip sdl2 sqlite3
vcpkg install ffmpeg[vpx,x264] libepoxy libpng libzip lua sdl2 sqlite3
Note that this installation won't support hardware accelerated video encoding on Nvidia hardware. If you care about this, you'll need to install CUDA beforehand, and then substitute `ffmpeg[vpx,x264,nvcodec]` into the previous command.
@ -243,6 +244,7 @@ medusa has no hard dependencies, however, the following optional dependencies ar
- libzip or zlib: for loading ROMs stored in zip files.
- SQLite3: for game databases.
- libelf: for ELF loading.
- Lua: for scripting.
SQLite3, libpng, and zlib are included with the emulator, so they do not need to be externally compiled first.

View File

@ -0,0 +1,80 @@
/* Copyright (c) 2013-2022 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_MACROS_H
#define M_MACROS_H
#define _mCPP_CAT(A, B) A ## B
#define _mIDENT(...) __VA_ARGS__
#define _mCALL(FN, ...) _mIDENT(FN(__VA_ARGS__))
#define _mCAT(A, B) _mCPP_CAT(A, B)
#define _mSTRINGIFY(X, ...) #X
#define _mCALL_0(FN, ...)
#define _mCALL_1(FN, A) FN(A)
#define _mCALL_2(FN, A, B) FN(A), FN(B)
#define _mCALL_3(FN, A, ...) FN(A), _mCALL_2(FN, __VA_ARGS__)
#define _mCALL_4(FN, A, ...) FN(A), _mCALL_3(FN, __VA_ARGS__)
#define _mCALL_5(FN, A, ...) FN(A), _mCALL_4(FN, __VA_ARGS__)
#define _mCALL_6(FN, A, ...) FN(A), _mCALL_5(FN, __VA_ARGS__)
#define _mCALL_7(FN, A, ...) FN(A), _mCALL_6(FN, __VA_ARGS__)
#define _mCALL_8(FN, A, ...) FN(A), _mCALL_7(FN, __VA_ARGS__)
#define _mCALL_9(FN, A, ...) FN(A), _mCALL_8(FN, __VA_ARGS__)
#define _mCOMMA_0(N, ...) N
#define _mCOMMA_1(N, ...) N, __VA_ARGS__
#define _mCOMMA_2(N, ...) N, __VA_ARGS__
#define _mCOMMA_3(N, ...) N, __VA_ARGS__
#define _mCOMMA_4(N, ...) N, __VA_ARGS__
#define _mCOMMA_5(N, ...) N, __VA_ARGS__
#define _mCOMMA_6(N, ...) N, __VA_ARGS__
#define _mCOMMA_7(N, ...) N, __VA_ARGS__
#define _mCOMMA_8(N, ...) N, __VA_ARGS__
#define _mCOMMA_9(N, ...) N, __VA_ARGS__
#define _mEVEN_0(...)
#define _mEVEN_1(A, B, ...) A
#define _mEVEN_2(A, B, ...) A, _mIDENT(_mEVEN_1(__VA_ARGS__))
#define _mEVEN_3(A, B, ...) A, _mIDENT(_mEVEN_2(__VA_ARGS__))
#define _mEVEN_4(A, B, ...) A, _mIDENT(_mEVEN_3(__VA_ARGS__))
#define _mEVEN_5(A, B, ...) A, _mIDENT(_mEVEN_4(__VA_ARGS__))
#define _mEVEN_6(A, B, ...) A, _mIDENT(_mEVEN_5(__VA_ARGS__))
#define _mEVEN_7(A, B, ...) A, _mIDENT(_mEVEN_6(__VA_ARGS__))
#define _mEVEN_8(A, B, ...) A, _mIDENT(_mEVEN_7(__VA_ARGS__))
#define _mEVEN_9(A, B, ...) A, _mIDENT(_mEVEN_7(__VA_ARGS__))
#define _mODD_0(...)
#define _mODD_1(A, B, ...) B
#define _mODD_2(A, B, ...) B, _mIDENT(_mODD_1(__VA_ARGS__))
#define _mODD_3(A, B, ...) B, _mIDENT(_mODD_2(__VA_ARGS__))
#define _mODD_4(A, B, ...) B, _mIDENT(_mODD_3(__VA_ARGS__))
#define _mODD_5(A, B, ...) B, _mIDENT(_mODD_4(__VA_ARGS__))
#define _mODD_6(A, B, ...) B, _mIDENT(_mODD_5(__VA_ARGS__))
#define _mODD_7(A, B, ...) B, _mIDENT(_mODD_6(__VA_ARGS__))
#define _mODD_8(A, B, ...) B, _mIDENT(_mODD_7(__VA_ARGS__))
#define _mODD_9(A, B, ...) B, _mIDENT(_mODD_7(__VA_ARGS__))
#define _mSUCC_0 1
#define _mSUCC_1 2
#define _mSUCC_2 3
#define _mSUCC_3 4
#define _mSUCC_4 5
#define _mSUCC_5 6
#define _mSUCC_6 7
#define _mSUCC_7 8
#define _mSUCC_8 9
#define _mPRED_1 0
#define _mPRED_2 1
#define _mPRED_3 2
#define _mPRED_4 3
#define _mPRED_5 4
#define _mPRED_6 5
#define _mPRED_7 6
#define _mPRED_8 7
#define _mPRED_9 8
#endif

View File

@ -70,7 +70,7 @@ void HashTableClear(struct Table*);
void HashTableEnumerate(const struct Table*, void (*handler)(const char* key, void* value, void* user), void* user);
void HashTableEnumerateBinary(const struct Table*, void (*handler)(const char* key, size_t keylen, void* value, void* user), void* user);
void HashTableEnumerateCustom(const struct Table*, void (*handler)(const char* key, void* value, void* user), void* user);
void HashTableEnumerateCustom(const struct Table*, void (*handler)(void* key, void* value, void* user), void* user);
const char* HashTableSearch(const struct Table* table, bool (*predicate)(const char* key, const void* value, const void* user), const void* user);
const char* HashTableSearchPointer(const struct Table* table, const void* value);
const char* HashTableSearchData(const struct Table* table, const void* value, size_t bytes);

View File

@ -192,6 +192,7 @@ struct VFile* mCoreGetState(struct mCore* core, int slot, bool write);
void mCoreDeleteState(struct mCore* core, int slot);
void mCoreTakeScreenshot(struct mCore* core);
bool mCoreTakeScreenshotVF(struct mCore* core, struct VFile* vf);
#endif
struct mCore* mCoreFindVF(struct VFile* vf);

View File

@ -56,6 +56,9 @@ int mLogFilterLevels(const struct mLogFilter*, int category);
ATTRIBUTE_FORMAT(printf, 3, 4)
void mLog(int category, enum mLogLevel level, const char* format, ...);
ATTRIBUTE_FORMAT(printf, 4, 5)
void mLogExplicit(struct mLogger*, int category, enum mLogLevel level, const char* format, ...);
#define mLOG(CATEGORY, LEVEL, ...) mLog(_mLOG_CAT_ ## CATEGORY, mLOG_ ## LEVEL, __VA_ARGS__)
#define mLOG_DECLARE_CATEGORY(CATEGORY) extern int _mLOG_CAT_ ## CATEGORY;

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2017 Jeffrey Pfau
/* Copyright (c) 2013-2022 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
@ -10,9 +10,21 @@
CXX_GUARD_START
#include <mgba/core/log.h>
#ifdef USE_DEBUGGERS
#include <mgba/debugger/debugger.h>
#endif
#include <mgba/script/macros.h>
#include <mgba/script/types.h>
mLOG_DECLARE_CATEGORY(SCRIPT);
struct mCore;
struct mScriptTextBuffer;
mSCRIPT_DECLARE_STRUCT(mCore);
mSCRIPT_DECLARE_STRUCT(mLogger);
mSCRIPT_DECLARE_STRUCT(mScriptConsole);
mSCRIPT_DECLARE_STRUCT(mScriptTextBuffer);
struct mScriptBridge;
struct VFile;
@ -31,6 +43,24 @@ struct mScriptEngine {
#endif
};
struct mScriptTextBuffer {
void (*init)(struct mScriptTextBuffer*, const char* name);
void (*deinit)(struct mScriptTextBuffer*);
void (*setName)(struct mScriptTextBuffer*, const char* text);
uint32_t (*getX)(const struct mScriptTextBuffer*);
uint32_t (*getY)(const struct mScriptTextBuffer*);
uint32_t (*cols)(const struct mScriptTextBuffer*);
uint32_t (*rows)(const struct mScriptTextBuffer*);
void (*print)(struct mScriptTextBuffer*, const char* text);
void (*clear)(struct mScriptTextBuffer*);
void (*setSize)(struct mScriptTextBuffer*, uint32_t cols, uint32_t rows);
void (*moveCursor)(struct mScriptTextBuffer*, uint32_t x, uint32_t y);
void (*advance)(struct mScriptTextBuffer*, int32_t);
};
struct mScriptBridge* mScriptBridgeCreate(void);
void mScriptBridgeDestroy(struct mScriptBridge*);
@ -47,6 +77,16 @@ bool mScriptBridgeLoadScript(struct mScriptBridge*, const char* name);
bool mScriptBridgeLookupSymbol(struct mScriptBridge*, const char* name, int32_t* out);
struct mScriptContext;
void mScriptContextAttachCore(struct mScriptContext*, struct mCore*);
void mScriptContextDetachCore(struct mScriptContext*);
void mScriptContextAttachLogger(struct mScriptContext*, struct mLogger*);
void mScriptContextDetachLogger(struct mScriptContext*);
typedef struct mScriptTextBuffer* (*mScriptContextBufferFactory)(void*);
void mScriptContextSetTextBufferFactory(struct mScriptContext*, mScriptContextBufferFactory factory, void* cbContext);
CXX_GUARD_END
#endif

View File

@ -26,6 +26,7 @@ enum mStateExtdataTag {
#define SAVESTATE_CHEATS 4
#define SAVESTATE_RTC 8
#define SAVESTATE_METADATA 16
#define SAVESTATE_ALL 31
struct mStateExtdataItem {
int32_t size;

View File

@ -23,6 +23,9 @@ struct mThreadLogger {
struct mCoreThread* p;
};
#ifdef ENABLE_SCRIPTING
struct mScriptContext;
#endif
struct mCoreThreadInternal;
struct mCoreThread {
// Input
@ -39,6 +42,10 @@ struct mCoreThread {
void* userData;
void (*run)(struct mCoreThread*);
#ifdef ENABLE_SCRIPTING
struct mScriptContext* scriptContext;
#endif
struct mCoreThreadInternal* impl;
};

View File

@ -27,7 +27,6 @@ extern const uint32_t SGB_SM83_FREQUENCY;
mLOG_DECLARE_CATEGORY(GB);
// TODO: Prefix GBAIRQ
enum GBIRQ {
GB_IRQ_VBLANK = 0x0,
GB_IRQ_LCDSTAT = 0x1,

View File

@ -22,20 +22,20 @@ CXX_GUARD_START
#define GBA_ARM7TDMI_FREQUENCY 0x1000000U
enum GBAIRQ {
IRQ_VBLANK = 0x0,
IRQ_HBLANK = 0x1,
IRQ_VCOUNTER = 0x2,
IRQ_TIMER0 = 0x3,
IRQ_TIMER1 = 0x4,
IRQ_TIMER2 = 0x5,
IRQ_TIMER3 = 0x6,
IRQ_SIO = 0x7,
IRQ_DMA0 = 0x8,
IRQ_DMA1 = 0x9,
IRQ_DMA2 = 0xA,
IRQ_DMA3 = 0xB,
IRQ_KEYPAD = 0xC,
IRQ_GAMEPAK = 0xD
GBA_IRQ_VBLANK = 0x0,
GBA_IRQ_HBLANK = 0x1,
GBA_IRQ_VCOUNTER = 0x2,
GBA_IRQ_TIMER0 = 0x3,
GBA_IRQ_TIMER1 = 0x4,
GBA_IRQ_TIMER2 = 0x5,
GBA_IRQ_TIMER3 = 0x6,
GBA_IRQ_SIO = 0x7,
GBA_IRQ_DMA0 = 0x8,
GBA_IRQ_DMA1 = 0x9,
GBA_IRQ_DMA2 = 0xA,
GBA_IRQ_DMA3 = 0xB,
GBA_IRQ_KEYPAD = 0xC,
GBA_IRQ_GAMEPAK = 0xD
};
enum GBAIdleLoopOptimization {
@ -45,9 +45,9 @@ enum GBAIdleLoopOptimization {
};
enum {
SP_BASE_SYSTEM = 0x03007F00,
SP_BASE_IRQ = 0x03007FA0,
SP_BASE_SUPERVISOR = 0x03007FE0
GBA_SP_BASE_SYSTEM = 0x03007F00,
GBA_SP_BASE_IRQ = 0x03007FA0,
GBA_SP_BASE_SUPERVISOR = 0x03007FE0
};
struct ARMCore;

View File

@ -0,0 +1,12 @@
/* Copyright (c) 2013-2022 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_LUA_H
#define M_SCRIPT_LUA_H
#include <mgba/script/context.h>
extern struct mScriptEngine2* const mSCRIPT_ENGINE_LUA;
#endif

View File

@ -0,0 +1,95 @@
/* Copyright (c) 2013-2022 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_CONTEXT_H
#define M_SCRIPT_CONTEXT_H
#include <mgba-util/common.h>
CXX_GUARD_START
#include <mgba/script/types.h>
#include <mgba-util/table.h>
#include <mgba-util/vfs.h>
#define mSCRIPT_CONSTANT_PAIR(NS, CONST) { #CONST, mScriptValueCreateFromSInt(NS ## _ ## CONST) }
#define mSCRIPT_CONSTANT_SENTINEL { NULL, NULL }
struct mScriptFrame;
struct mScriptFunction;
struct mScriptEngineContext;
struct mScriptContext {
struct Table rootScope;
struct Table engines;
struct mScriptList refPool;
struct Table weakrefs;
uint32_t nextWeakref;
struct Table callbacks;
struct mScriptValue* constants;
};
struct mScriptEngine2 {
const char* name;
void (*init)(struct mScriptEngine2*);
void (*deinit)(struct mScriptEngine2*);
struct mScriptEngineContext* (*create)(struct mScriptEngine2*, struct mScriptContext*);
};
struct mScriptEngineContext {
struct mScriptContext* context;
void (*destroy)(struct mScriptEngineContext*);
bool (*isScript)(struct mScriptEngineContext*, const char* name, struct VFile* vf);
bool (*setGlobal)(struct mScriptEngineContext*, const char* name, struct mScriptValue*);
struct mScriptValue* (*getGlobal)(struct mScriptEngineContext*, const char* name);
bool (*load)(struct mScriptEngineContext*, const char* filename, struct VFile*);
bool (*run)(struct mScriptEngineContext*);
const char* (*getError)(struct mScriptEngineContext*);
};
struct mScriptKVPair {
const char* key;
struct mScriptValue* value;
};
void mScriptContextInit(struct mScriptContext*);
void mScriptContextDeinit(struct mScriptContext*);
void mScriptContextFillPool(struct mScriptContext*, struct mScriptValue*);
void mScriptContextDrainPool(struct mScriptContext*);
struct mScriptEngineContext* mScriptContextRegisterEngine(struct mScriptContext*, struct mScriptEngine2*);
void mScriptContextRegisterEngines(struct mScriptContext*);
void mScriptContextSetGlobal(struct mScriptContext*, const char* key, struct mScriptValue* value);
struct mScriptValue* mScriptContextGetGlobal(struct mScriptContext*, const char* key);
void mScriptContextRemoveGlobal(struct mScriptContext*, const char* key);
struct mScriptValue* mScriptContextEnsureGlobal(struct mScriptContext*, const char* key, const struct mScriptType* type);
uint32_t mScriptContextSetWeakref(struct mScriptContext*, struct mScriptValue* value);
struct mScriptValue* mScriptContextMakeWeakref(struct mScriptContext*, struct mScriptValue* value);
struct mScriptValue* mScriptContextAccessWeakref(struct mScriptContext*, struct mScriptValue* value);
void mScriptContextClearWeakref(struct mScriptContext*, uint32_t weakref);
void mScriptContextAttachStdlib(struct mScriptContext* context);
void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants);
void mScriptContextTriggerCallback(struct mScriptContext*, const char* callback);
void mScriptContextAddCallback(struct mScriptContext*, const char* callback, struct mScriptValue* value);
struct VFile;
bool mScriptContextLoadVF(struct mScriptContext*, const char* name, struct VFile* vf);
bool mScriptContextLoadFile(struct mScriptContext*, const char* path);
bool mScriptInvoke(const struct mScriptValue* fn, struct mScriptFrame* frame);
CXX_GUARD_END
#endif

View File

@ -0,0 +1,488 @@
/* Copyright (c) 2013-2022 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_MACROS_H
#define M_SCRIPT_MACROS_H
#include <mgba-util/common.h>
CXX_GUARD_START
#define mSCRIPT_POP(STACK, TYPE, NAME) \
mSCRIPT_TYPE_C_ ## TYPE NAME; \
do { \
struct mScriptValue* _val = mScriptListGetPointer(STACK, mScriptListSize(STACK) - 1); \
bool deref = true; \
if (!(mSCRIPT_TYPE_CMP(TYPE, _val->type))) { \
if (_val->type->base == mSCRIPT_TYPE_WRAPPER) { \
_val = mScriptValueUnwrap(_val); \
deref = false; \
if (!(mSCRIPT_TYPE_CMP(TYPE, _val->type))) { \
return false; \
} \
} else { \
return false; \
} \
} \
NAME = _val->value.mSCRIPT_TYPE_FIELD_ ## TYPE; \
if (deref) { \
mScriptValueDeref(_val); \
} \
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 = mSCRIPT_TYPE_MS_ ## TYPE; \
_val->refs = mSCRIPT_VALUE_UNREF; \
_val->flags = 0; \
_val->value.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_PREFIX_N(N) mSCRIPT_PREFIX_ ## N
#define _mSCRIPT_FIELD_NAME(V) (V)->name
#define _mSCRIPT_CALL_VOID(FUNCTION, NPARAMS) FUNCTION(_mCAT(mSCRIPT_ARG_NAMES_, NPARAMS))
#define _mSCRIPT_CALL(RETURN, FUNCTION, NPARAMS) \
mSCRIPT_TYPE_C_ ## RETURN out = FUNCTION(_mCAT(mSCRIPT_ARG_NAMES_, NPARAMS)); \
mSCRIPT_PUSH(&frame->returnValues, RETURN, out)
#define mSCRIPT_DECLARE_STRUCT(STRUCT) \
extern const struct mScriptType mSTStruct_ ## STRUCT; \
extern const struct mScriptType mSTStructConst_ ## STRUCT; \
extern const struct mScriptType mSTStructPtr_ ## STRUCT; \
extern const struct mScriptType mSTStructPtrConst_ ## STRUCT;
#define mSCRIPT_DEFINE_STRUCT(STRUCT) \
const struct mScriptType mSTStruct_ ## STRUCT; \
const struct mScriptType mSTStructConst_ ## STRUCT; \
const struct mScriptType mSTStructPtr_ ## STRUCT; \
const struct mScriptType mSTStructPtrConst_ ## STRUCT; \
static struct mScriptTypeClass _mSTStructDetails_ ## STRUCT; \
static bool _mSTStructPtrCast_ ## STRUCT(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) { \
if (input->type == type || (input->type->constType == type)) { \
output->type = type; \
output->value.opaque = input->value.opaque; \
return true; \
} \
if (input->type != &mSTStructPtr_ ## STRUCT && input->type != &mSTStructPtrConst_ ## STRUCT) { \
return false; \
} \
if (type == &mSTStructConst_ ## STRUCT || (!input->type->isConst && type == &mSTStruct_ ## STRUCT)) { \
output->type = type; \
output->value.opaque = *(void**) input->value.opaque; \
return true; \
} \
return false; \
} \
const struct mScriptType mSTStruct_ ## STRUCT = { \
.base = mSCRIPT_TYPE_OBJECT, \
.details = { \
.cls = &_mSTStructDetails_ ## STRUCT \
}, \
.size = sizeof(struct STRUCT), \
.name = "struct::" #STRUCT, \
.alloc = NULL, \
.free = mScriptObjectFree, \
.cast = mScriptObjectCast, \
.constType = &mSTStructConst_ ## STRUCT, \
}; \
const struct mScriptType mSTStructConst_ ## STRUCT = { \
.base = mSCRIPT_TYPE_OBJECT, \
.isConst = true, \
.details = { \
.cls = &_mSTStructDetails_ ## STRUCT \
}, \
.size = sizeof(struct STRUCT), \
.name = "const struct::" #STRUCT, \
.alloc = NULL, \
.free = NULL, \
.cast = mScriptObjectCast, \
}; \
const struct mScriptType mSTStructPtr_ ## STRUCT = { \
.base = mSCRIPT_TYPE_OPAQUE, \
.details = { \
.type = &mSTStruct_ ## STRUCT \
}, \
.size = sizeof(void*), \
.name = "ptr struct::" #STRUCT, \
.alloc = NULL, \
.free = NULL, \
.cast = _mSTStructPtrCast_ ## STRUCT, \
.constType = &mSTStructPtrConst_ ## STRUCT, \
}; \
const struct mScriptType mSTStructPtrConst_ ## STRUCT = { \
.base = mSCRIPT_TYPE_OPAQUE, \
.details = { \
.type = &mSTStructConst_ ## STRUCT \
}, \
.isConst = true, \
.size = sizeof(void*), \
.name = "ptr const struct::" #STRUCT, \
.alloc = NULL, \
.free = NULL, \
.cast = _mSTStructPtrCast_ ## STRUCT, \
}; \
const struct mScriptType mSTWrapper_ ## STRUCT = { \
.base = mSCRIPT_TYPE_WRAPPER, \
.details = { \
.type = &mSTStruct_ ## STRUCT \
}, \
.size = sizeof(struct mScriptValue), \
.name = "wrapper struct::" #STRUCT, \
.alloc = NULL, \
.free = NULL, \
}; \
const struct mScriptType mSTWrapperConst_ ## STRUCT = { \
.base = mSCRIPT_TYPE_WRAPPER, \
.details = { \
.type = &mSTStructConst_ ## STRUCT \
}, \
.size = sizeof(struct mScriptValue), \
.name = "wrapper const struct::" #STRUCT, \
.alloc = NULL, \
.free = NULL, \
}; \
static struct mScriptTypeClass _mSTStructDetails_ ## STRUCT = { \
.init = false, \
.details = (const struct mScriptClassInitDetails[]) {
#define mSCRIPT_DEFINE_DOCSTRING(DOCSTRING) { \
.type = mSCRIPT_CLASS_INIT_DOCSTRING, \
.info = { \
.comment = DOCSTRING \
} \
},
#define mSCRIPT_DEFINE_CLASS_DOCSTRING(DOCSTRING) { \
.type = mSCRIPT_CLASS_INIT_CLASS_DOCSTRING, \
.info = { \
.comment = DOCSTRING \
} \
},
#define mSCRIPT_DEFINE_STRUCT_MEMBER_NAMED(STRUCT, TYPE, EXPORTED_NAME, NAME) { \
.type = mSCRIPT_CLASS_INIT_INSTANCE_MEMBER, \
.info = { \
.member = { \
.name = #EXPORTED_NAME, \
.type = mSCRIPT_TYPE_MS_ ## TYPE, \
.offset = offsetof(struct STRUCT, NAME) \
} \
} \
},
#define mSCRIPT_DEFINE_STRUCT_MEMBER(STRUCT, TYPE, NAME) \
mSCRIPT_DEFINE_STRUCT_MEMBER_NAMED(STRUCT, TYPE, NAME, NAME)
#define mSCRIPT_DEFINE_INHERIT(PARENT) { \
.type = mSCRIPT_CLASS_INIT_INHERIT, \
.info = { \
.parent = mSCRIPT_TYPE_MS_S(PARENT) \
} \
},
#define _mSCRIPT_STRUCT_METHOD_POP(TYPE, S, NPARAMS, ...) \
_mCALL(_mCAT(mSCRIPT_POP_, _mSUCC_ ## NPARAMS), &frame->arguments, _mCOMMA_ ## NPARAMS(S(TYPE), __VA_ARGS__)); \
if (mScriptListSize(&frame->arguments)) { \
return false; \
}
#define _mSCRIPT_DECLARE_STRUCT_METHOD(TYPE, NAME, S, NRET, RETURN, NPARAMS, DEFAULTS, ...) \
static bool _mSTStructBinding_ ## TYPE ## _ ## NAME(struct mScriptFrame* frame, void* ctx); \
static const struct mScriptFunction _mSTStructBindingFunction_ ## TYPE ## _ ## NAME = { \
.call = &_mSTStructBinding_ ## TYPE ## _ ## NAME \
}; \
\
static void _mSTStructBindingAlloc_ ## TYPE ## _ ## NAME(struct mScriptValue* val) { \
val->value.copaque = &_mSTStructBindingFunction_ ## TYPE ## _ ## NAME; \
}\
static const struct mScriptType _mSTStructBindingType_ ## TYPE ## _ ## NAME = { \
.base = mSCRIPT_TYPE_FUNCTION, \
.name = "struct::" #TYPE "." #NAME, \
.alloc = _mSTStructBindingAlloc_ ## TYPE ## _ ## NAME, \
.details = { \
.function = { \
.parameters = { \
.count = _mSUCC_ ## NPARAMS, \
.entries = { mSCRIPT_TYPE_MS_ ## S(TYPE), _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \
.names = { "this", _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \
.defaults = DEFAULTS, \
}, \
.returnType = { \
.count = NRET, \
.entries = { RETURN } \
}, \
}, \
} \
};
#define _mSCRIPT_DECLARE_STRUCT_METHOD_SIGNATURE(TYPE, RETURN, NAME, CONST, NPARAMS, ...) \
typedef RETURN (*_mSTStructFunctionType_ ## TYPE ## _ ## NAME)(_mCOMMA_ ## NPARAMS(CONST struct TYPE* , mSCRIPT_PREFIX_ ## NPARAMS(mSCRIPT_TYPE_C_, __VA_ARGS__)))
#define _mSCRIPT_DECLARE_STRUCT_METHOD_BINDING(TYPE, RETURN, NAME, FUNCTION, T, NPARAMS, ...) \
static bool _mSTStructBinding_ ## TYPE ## _ ## NAME(struct mScriptFrame* frame, void* ctx) { \
UNUSED(ctx); \
_mSCRIPT_STRUCT_METHOD_POP(TYPE, T, NPARAMS, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
_mSCRIPT_CALL(RETURN, FUNCTION, _mSUCC_ ## NPARAMS); \
return true; \
}
#define _mSCRIPT_DECLARE_STRUCT_VOID_METHOD_BINDING(TYPE, NAME, FUNCTION, T, NPARAMS, ...) \
static bool _mSTStructBinding_ ## TYPE ## _ ## NAME(struct mScriptFrame* frame, void* ctx) { \
UNUSED(ctx); \
_mSCRIPT_STRUCT_METHOD_POP(TYPE, T, NPARAMS, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
_mSCRIPT_CALL_VOID(FUNCTION, _mSUCC_ ## NPARAMS); \
return true; \
} \
#define mSCRIPT_DECLARE_STRUCT_METHOD(TYPE, RETURN, NAME, FUNCTION, NPARAMS, ...) \
_mSCRIPT_DECLARE_STRUCT_METHOD_SIGNATURE(TYPE, mSCRIPT_TYPE_C_ ## RETURN, NAME, , NPARAMS, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
_mSCRIPT_DECLARE_STRUCT_METHOD(TYPE, NAME, S, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, NULL, __VA_ARGS__) \
_mSCRIPT_DECLARE_STRUCT_METHOD_BINDING(TYPE, RETURN, NAME, FUNCTION, S, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TYPE, NAME, FUNCTION, NPARAMS, ...) \
_mSCRIPT_DECLARE_STRUCT_METHOD_SIGNATURE(TYPE, void, NAME, , NPARAMS, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
_mSCRIPT_DECLARE_STRUCT_METHOD(TYPE, NAME, S, 0, 0, NPARAMS, NULL, __VA_ARGS__) \
_mSCRIPT_DECLARE_STRUCT_VOID_METHOD_BINDING(TYPE, NAME, FUNCTION, S, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_C_METHOD(TYPE, RETURN, NAME, FUNCTION, NPARAMS, ...) \
_mSCRIPT_DECLARE_STRUCT_METHOD_SIGNATURE(TYPE, mSCRIPT_TYPE_C_ ## RETURN, NAME, const, NPARAMS, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
_mSCRIPT_DECLARE_STRUCT_METHOD(TYPE, NAME, CS, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, NULL, __VA_ARGS__) \
_mSCRIPT_DECLARE_STRUCT_METHOD_BINDING(TYPE, RETURN, NAME, FUNCTION, CS, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_VOID_C_METHOD(TYPE, NAME, FUNCTION, NPARAMS, ...) \
_mSCRIPT_DECLARE_STRUCT_METHOD_SIGNATURE(TYPE, void, NAME, const, NPARAMS, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
_mSCRIPT_DECLARE_STRUCT_METHOD(TYPE, NAME, CS, 0, 0, NPARAMS, NULL, __VA_ARGS__) \
_mSCRIPT_DECLARE_STRUCT_VOID_METHOD_BINDING(TYPE, NAME, FUNCTION, CS, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(TYPE, RETURN, NAME, FUNCTION, NPARAMS, ...) \
static const struct mScriptValue _mSTStructBindingDefaults_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \
_mSCRIPT_DECLARE_STRUCT_METHOD_SIGNATURE(TYPE, mSCRIPT_TYPE_C_ ## RETURN, NAME, , NPARAMS, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
_mSCRIPT_DECLARE_STRUCT_METHOD(TYPE, NAME, S, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, _mIDENT(_mSTStructBindingDefaults_ ## TYPE ## _ ## NAME), __VA_ARGS__) \
_mSCRIPT_DECLARE_STRUCT_METHOD_BINDING(TYPE, RETURN, NAME, FUNCTION, S, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_VOID_METHOD_WITH_DEFAULTS(TYPE, NAME, FUNCTION, NPARAMS, ...) \
static const struct mScriptValue _mSTStructBindingDefaults_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \
_mSCRIPT_DECLARE_STRUCT_METHOD_SIGNATURE(TYPE, void, NAME, , NPARAMS, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
_mSCRIPT_DECLARE_STRUCT_METHOD(TYPE, NAME, S, 0, 0, NPARAMS, _mIDENT(_mSTStructBindingDefaults_ ## TYPE ## _ ## NAME), __VA_ARGS__) \
_mSCRIPT_DECLARE_STRUCT_VOID_METHOD_BINDING(TYPE, NAME, FUNCTION, S, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_C_METHOD_WITH_DEFAULTS(TYPE, RETURN, NAME, FUNCTION, NPARAMS, ...) \
static const struct mScriptValue _mSTStructBindingDefaults_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \
_mSCRIPT_DECLARE_STRUCT_METHOD_SIGNATURE(TYPE, mSCRIPT_TYPE_C_ ## RETURN, NAME, const, NPARAMS, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
_mSCRIPT_DECLARE_STRUCT_METHOD(TYPE, NAME, CS, 1, mSCRIPT_TYPE_MS_##RETURN, NPARAMS, _mIDENT(_mSTStructBindingDefaults_ ## TYPE ## _ ## NAME), __VA_ARGS__) \
_mSCRIPT_DECLARE_STRUCT_METHOD_BINDING(TYPE, RETURN, NAME, FUNCTION, CS, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_VOID_C_METHOD_WITH_DEFAULTS(TYPE, NAME, FUNCTION, NPARAMS, ...) \
static const struct mScriptValue _mSTStructBindingDefaults_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX]; \
_mSCRIPT_DECLARE_STRUCT_METHOD_SIGNATURE(TYPE, void, NAME, const, NPARAMS, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
_mSCRIPT_DECLARE_STRUCT_METHOD(TYPE, NAME, CS, 0, 0, NPARAMS, _mIDENT(_mSTStructBindingDefaults_ ## TYPE ## _ ## NAME, __VA_ARGS__) \
_mSCRIPT_DECLARE_STRUCT_VOID_METHOD_BINDING(TYPE, NAME, FUNCTION, CS, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_D_METHOD(TYPE, RETURN, NAME, NPARAMS, ...) \
mSCRIPT_DECLARE_STRUCT_METHOD(TYPE, RETURN, NAME, p0->NAME, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(TYPE, NAME, NPARAMS, ...) \
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TYPE, NAME, p0->NAME, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_CD_METHOD(TYPE, RETURN, NAME, NPARAMS, ...) \
mSCRIPT_DECLARE_STRUCT_C_METHOD(TYPE, RETURN, NAME, p0->NAME, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_VOID_CD_METHOD(TYPE, NAME, NPARAMS, ...) \
mSCRIPT_DECLARE_STRUCT_VOID_C_METHOD(TYPE, NAME, p0->NAME, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_D_METHOD_WITH_DEFAULTS(TYPE, RETURN, NAME, NPARAMS, ...) \
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(TYPE, RETURN, NAME, p0->NAME, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD_WITH_DEFAULTS(TYPE, NAME, NPARAMS, ...) \
mSCRIPT_DECLARE_STRUCT_VOID_METHOD_WITH_DEFAULTS(TYPE, NAME, p0->NAME, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_CD_METHOD_WITH_DEFAULTS(TYPE, RETURN, NAME, NPARAMS, ...) \
mSCRIPT_DECLARE_STRUCT_C_METHOD_WITH_DEFAULTS(TYPE, RETURN, NAME, p0->NAME, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DECLARE_STRUCT_VOID_CD_METHOD_WITH_DEFAULTS(TYPE, NAME, NPARAMS, ...) \
mSCRIPT_DECLARE_STRUCT_VOID_C_METHOD_WITH_DEFAU(TYPE, NAME, p0->NAME, NPARAMS, __VA_ARGS__)
#define mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(TYPE, NAME) \
static const struct mScriptValue _mSTStructBindingDefaults_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX] = { \
mSCRIPT_NO_DEFAULT,
#define mSCRIPT_DEFINE_DEFAULTS_END }
#define _mSCRIPT_DEFINE_STRUCT_BINDING(INIT_TYPE, TYPE, EXPORTED_NAME, NAME) { \
.type = mSCRIPT_CLASS_INIT_ ## INIT_TYPE, \
.info = { \
.member = { \
.name = #EXPORTED_NAME, \
.type = &_mSTStructBindingType_ ## TYPE ## _ ## NAME \
} \
}, \
},
#define mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(TYPE, EXPORTED_NAME, NAME) \
_mSCRIPT_DEFINE_STRUCT_BINDING(INSTANCE_MEMBER, TYPE, EXPORTED_NAME, NAME)
#define mSCRIPT_DEFINE_STRUCT_METHOD(TYPE, NAME) mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(TYPE, NAME, NAME)
#define mSCRIPT_DEFINE_STRUCT_INIT(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(INIT, TYPE, _init, _init)
#define mSCRIPT_DEFINE_STRUCT_INIT_NAMED(TYPE, NAME) _mSCRIPT_DEFINE_STRUCT_BINDING(INIT, TYPE, _init, NAME)
#define mSCRIPT_DEFINE_STRUCT_DEINIT(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(DEINIT, TYPE, _deinit, _deinit)
#define mSCRIPT_DEFINE_STRUCT_DEINIT_NAMED(TYPE, NAME) _mSCRIPT_DEFINE_STRUCT_BINDING(DEINIT, TYPE, _deinit, NAME)
#define mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(GET, TYPE, _get, _get)
#define mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(TYPE) _mSCRIPT_DEFINE_STRUCT_BINDING(SET, TYPE, _set, _set)
#define mSCRIPT_DEFINE_STRUCT_CAST_TO_MEMBER(TYPE, CAST_TYPE, MEMBER) { \
.type = mSCRIPT_CLASS_INIT_CAST_TO_MEMBER, \
.info = { \
.castMember = { \
.type = mSCRIPT_TYPE_MS_ ## CAST_TYPE, \
.member = #MEMBER \
} \
}, \
},
#define mSCRIPT_DEFINE_END { .type = mSCRIPT_CLASS_INIT_END } } }
#define _mSCRIPT_BIND_FUNCTION(NAME, NRET, RETURN, NPARAMS, ...) \
static struct mScriptFunction _function_ ## NAME = { \
.call = _binding_ ## NAME \
}; \
static void _alloc_ ## NAME(struct mScriptValue* val) { \
val->value.copaque = &_function_ ## NAME; \
} \
static const struct mScriptType _type_ ## NAME = { \
.base = mSCRIPT_TYPE_FUNCTION, \
.name = "function::" #NAME, \
.alloc = _alloc_ ## NAME, \
.details = { \
.function = { \
.parameters = { \
.count = NPARAMS, \
.entries = { _mCALL(mSCRIPT_PREFIX_ ## NPARAMS, mSCRIPT_TYPE_MS_, _mEVEN_ ## NPARAMS(__VA_ARGS__)) }, \
.names = { _mCALL(_mCALL_ ## NPARAMS, _mSTRINGIFY, _mODD_ ## NPARAMS(__VA_ARGS__)) }, \
}, \
.returnType = { \
.count = NRET, \
.entries = { RETURN } \
}, \
}, \
} \
}; \
const struct mScriptValue NAME = { \
.type = &_type_ ## NAME, \
.refs = mSCRIPT_VALUE_UNREF, \
.value = { \
.copaque = &_function_ ## NAME \
} \
}
#define mSCRIPT_BIND_FUNCTION(NAME, RETURN, FUNCTION, NPARAMS, ...) \
static bool _binding_ ## NAME(struct mScriptFrame* frame, void* ctx) { \
UNUSED(ctx); \
_mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->arguments, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
if (mScriptListSize(&frame->arguments)) { \
return false; \
} \
_mSCRIPT_CALL(RETURN, FUNCTION, NPARAMS); \
return true; \
} \
_mSCRIPT_BIND_FUNCTION(NAME, 1, mSCRIPT_TYPE_MS_ ## RETURN, NPARAMS, __VA_ARGS__)
#define mSCRIPT_BIND_VOID_FUNCTION(NAME, FUNCTION, NPARAMS, ...) \
static bool _binding_ ## NAME(struct mScriptFrame* frame, void* ctx) { \
UNUSED(ctx); \
_mCALL(mSCRIPT_POP_ ## NPARAMS, &frame->arguments, _mEVEN_ ## NPARAMS(__VA_ARGS__)); \
if (mScriptListSize(&frame->arguments)) { \
return false; \
} \
_mSCRIPT_CALL_VOID(FUNCTION, NPARAMS); \
return true; \
} \
_mSCRIPT_BIND_FUNCTION(NAME, 0, , NPARAMS, __VA_ARGS__)
#define mSCRIPT_MAKE(TYPE, VALUE) (struct mScriptValue) { \
.type = (mSCRIPT_TYPE_MS_ ## TYPE), \
.refs = mSCRIPT_VALUE_UNREF, \
.value = { \
.mSCRIPT_TYPE_FIELD_ ## TYPE = (VALUE) \
}, \
} \
#define mSCRIPT_VAL(TYPE, VALUE) { \
.type = (mSCRIPT_TYPE_MS_ ## TYPE), \
.refs = mSCRIPT_VALUE_UNREF, \
.value = { \
.mSCRIPT_TYPE_FIELD_ ## TYPE = (VALUE) \
}, \
} \
#define mSCRIPT_MAKE_S8(VALUE) mSCRIPT_MAKE(S8, VALUE)
#define mSCRIPT_MAKE_U8(VALUE) mSCRIPT_MAKE(U8, VALUE)
#define mSCRIPT_MAKE_S16(VALUE) mSCRIPT_MAKE(S16, VALUE)
#define mSCRIPT_MAKE_U16(VALUE) mSCRIPT_MAKE(U16, VALUE)
#define mSCRIPT_MAKE_S32(VALUE) mSCRIPT_MAKE(S32, VALUE)
#define mSCRIPT_MAKE_U32(VALUE) mSCRIPT_MAKE(U32, VALUE)
#define mSCRIPT_MAKE_F32(VALUE) mSCRIPT_MAKE(F32, VALUE)
#define mSCRIPT_MAKE_S64(VALUE) mSCRIPT_MAKE(S64, VALUE)
#define mSCRIPT_MAKE_U64(VALUE) mSCRIPT_MAKE(U64, VALUE)
#define mSCRIPT_MAKE_F64(VALUE) mSCRIPT_MAKE(F64, VALUE)
#define mSCRIPT_MAKE_CHARP(VALUE) mSCRIPT_MAKE(CHARP, VALUE)
#define mSCRIPT_MAKE_S(STRUCT, VALUE) mSCRIPT_MAKE(S(STRUCT), VALUE)
#define mSCRIPT_MAKE_CS(STRUCT, VALUE) mSCRIPT_MAKE(CS(STRUCT), VALUE)
#define mSCRIPT_S8(VALUE) mSCRIPT_VAL(S8, VALUE)
#define mSCRIPT_U8(VALUE) mSCRIPT_VAL(U8, VALUE)
#define mSCRIPT_S16(VALUE) mSCRIPT_VAL(S16, VALUE)
#define mSCRIPT_U16(VALUE) mSCRIPT_VAL(U16, VALUE)
#define mSCRIPT_S32(VALUE) mSCRIPT_VAL(S32, VALUE)
#define mSCRIPT_U32(VALUE) mSCRIPT_VAL(U32, VALUE)
#define mSCRIPT_F32(VALUE) mSCRIPT_VAL(F32, VALUE)
#define mSCRIPT_S64(VALUE) mSCRIPT_VAL(S64, VALUE)
#define mSCRIPT_U64(VALUE) mSCRIPT_VAL(U64, VALUE)
#define mSCRIPT_F64(VALUE) mSCRIPT_VAL(F64, VALUE)
#define mSCRIPT_CHARP(VALUE) mSCRIPT_VAL(CHARP, VALUE)
#define mSCRIPT_S(STRUCT, VALUE) mSCRIPT_VAL(S(STRUCT), VALUE)
#define mSCRIPT_CS(STRUCT, VALUE) mSCRIPT_VAL(CS(STRUCT), VALUE)
#define mSCRIPT_NO_DEFAULT { \
.type = NULL, \
.refs = mSCRIPT_VALUE_UNREF, \
.value = {0} \
}
CXX_GUARD_END
#endif

331
include/mgba/script/types.h Normal file
View File

@ -0,0 +1,331 @@
/* 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/macros.h>
#include <mgba-util/table.h>
#include <mgba-util/vector.h>
#define mSCRIPT_VALUE_UNREF -1
#define mSCRIPT_PARAMS_MAX 8
#define mSCRIPT_TYPE_C_S8 int8_t
#define mSCRIPT_TYPE_C_U8 uint8_t
#define mSCRIPT_TYPE_C_S16 int16_t
#define mSCRIPT_TYPE_C_U16 uint16_t
#define mSCRIPT_TYPE_C_S32 int32_t
#define mSCRIPT_TYPE_C_U32 uint32_t
#define mSCRIPT_TYPE_C_F32 float
#define mSCRIPT_TYPE_C_S64 int64_t
#define mSCRIPT_TYPE_C_U64 uint64_t
#define mSCRIPT_TYPE_C_F64 double
#define mSCRIPT_TYPE_C_STR struct mScriptString*
#define mSCRIPT_TYPE_C_CHARP const char*
#define mSCRIPT_TYPE_C_PTR void*
#define mSCRIPT_TYPE_C_CPTR const void*
#define mSCRIPT_TYPE_C_LIST struct mScriptList*
#define mSCRIPT_TYPE_C_TABLE Table*
#define mSCRIPT_TYPE_C_WRAPPER struct mScriptValue*
#define mSCRIPT_TYPE_C_WEAKREF uint32_t
#define mSCRIPT_TYPE_C_S(STRUCT) struct STRUCT*
#define mSCRIPT_TYPE_C_CS(STRUCT) const struct STRUCT*
#define mSCRIPT_TYPE_C_S_METHOD(STRUCT, NAME) _mSTStructFunctionType_ ## STRUCT ## _ ## NAME
#define mSCRIPT_TYPE_C_PS(X) void
#define mSCRIPT_TYPE_C_PCS(X) void
#define mSCRIPT_TYPE_C_WSTR struct mScriptValue*
#define mSCRIPT_TYPE_C_W(X) struct mScriptValue*
#define mSCRIPT_TYPE_C_CW(X) const struct mScriptValue*
#define mSCRIPT_TYPE_FIELD_S8 s32
#define mSCRIPT_TYPE_FIELD_U8 s32
#define mSCRIPT_TYPE_FIELD_S16 s32
#define mSCRIPT_TYPE_FIELD_U16 s32
#define mSCRIPT_TYPE_FIELD_S32 s32
#define mSCRIPT_TYPE_FIELD_U32 u32
#define mSCRIPT_TYPE_FIELD_F32 f32
#define mSCRIPT_TYPE_FIELD_S64 s64
#define mSCRIPT_TYPE_FIELD_U64 u64
#define mSCRIPT_TYPE_FIELD_F64 f64
#define mSCRIPT_TYPE_FIELD_STR string
#define mSCRIPT_TYPE_FIELD_CHARP copaque
#define mSCRIPT_TYPE_FIELD_PTR opaque
#define mSCRIPT_TYPE_FIELD_LIST list
#define mSCRIPT_TYPE_FIELD_TABLE table
#define mSCRIPT_TYPE_FIELD_WRAPPER opaque
#define mSCRIPT_TYPE_FIELD_WEAKREF u32
#define mSCRIPT_TYPE_FIELD_S(STRUCT) opaque
#define mSCRIPT_TYPE_FIELD_CS(STRUCT) copaque
#define mSCRIPT_TYPE_FIELD_S_METHOD(STRUCT, NAME) copaque
#define mSCRIPT_TYPE_FIELD_PS(STRUCT) opaque
#define mSCRIPT_TYPE_FIELD_PCS(STRUCT) copaque
#define mSCRIPT_TYPE_FIELD_WSTR opaque
#define mSCRIPT_TYPE_FIELD_W(TYPE) opaque
#define mSCRIPT_TYPE_FIELD_CW(TYPE) opaque
#define mSCRIPT_TYPE_MS_S8 (&mSTSInt8)
#define mSCRIPT_TYPE_MS_U8 (&mSTUInt8)
#define mSCRIPT_TYPE_MS_S16 (&mSTSInt16)
#define mSCRIPT_TYPE_MS_U16 (&mSTUInt16)
#define mSCRIPT_TYPE_MS_S32 (&mSTSInt32)
#define mSCRIPT_TYPE_MS_U32 (&mSTUInt32)
#define mSCRIPT_TYPE_MS_F32 (&mSTFloat32)
#define mSCRIPT_TYPE_MS_S64 (&mSTSInt64)
#define mSCRIPT_TYPE_MS_U64 (&mSTUInt64)
#define mSCRIPT_TYPE_MS_F64 (&mSTFloat64)
#define mSCRIPT_TYPE_MS_STR (&mSTString)
#define mSCRIPT_TYPE_MS_CHARP (&mSTCharPtr)
#define mSCRIPT_TYPE_MS_LIST (&mSTList)
#define mSCRIPT_TYPE_MS_TABLE (&mSTTable)
#define mSCRIPT_TYPE_MS_WRAPPER (&mSTWrapper)
#define mSCRIPT_TYPE_MS_WEAKREF (&mSTWeakref)
#define mSCRIPT_TYPE_MS_S(STRUCT) (&mSTStruct_ ## STRUCT)
#define mSCRIPT_TYPE_MS_CS(STRUCT) (&mSTStructConst_ ## STRUCT)
#define mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME) (&_mSTStructBindingType_ ## STRUCT ## _ ## NAME)
#define mSCRIPT_TYPE_MS_PS(STRUCT) (&mSTStructPtr_ ## STRUCT)
#define mSCRIPT_TYPE_MS_PCS(STRUCT) (&mSTStructConstPtr_ ## STRUCT)
#define mSCRIPT_TYPE_MS_WSTR (&mSTStringWrapper)
#define mSCRIPT_TYPE_MS_W(TYPE) (&mSTWrapper_ ## TYPE)
#define mSCRIPT_TYPE_MS_CW(TYPE) (&mSTWrapperConst_ ## TYPE)
#define mSCRIPT_TYPE_CMP_GENERIC(TYPE0, TYPE1) (TYPE0 == TYPE1)
#define mSCRIPT_TYPE_CMP_U8(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_U8, TYPE)
#define mSCRIPT_TYPE_CMP_S8(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_S8, TYPE)
#define mSCRIPT_TYPE_CMP_U16(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_U16, TYPE)
#define mSCRIPT_TYPE_CMP_S16(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_S16, TYPE)
#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_U64(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_U64, TYPE)
#define mSCRIPT_TYPE_CMP_S64(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_S64, TYPE)
#define mSCRIPT_TYPE_CMP_F64(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_F64, TYPE)
#define mSCRIPT_TYPE_CMP_STR(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE)
#define mSCRIPT_TYPE_CMP_CHARP(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE)
#define mSCRIPT_TYPE_CMP_PTR(TYPE) ((TYPE)->base >= mSCRIPT_TYPE_OPAQUE)
#define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true)
#define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME
#define mSCRIPT_TYPE_CMP_CS(STRUCT) mSCRIPT_TYPE_MS_CS(STRUCT)->name == _mSCRIPT_FIELD_NAME
#define mSCRIPT_TYPE_CMP_S_METHOD(STRUCT, NAME) mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME)->name == _mSCRIPT_FIELD_NAME
#define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1)
#define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) || mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE))
enum mScriptTypeBase {
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_LIST,
mSCRIPT_TYPE_TABLE,
mSCRIPT_TYPE_WRAPPER,
mSCRIPT_TYPE_WEAKREF,
};
enum mScriptClassInitType {
mSCRIPT_CLASS_INIT_END = 0,
mSCRIPT_CLASS_INIT_CLASS_DOCSTRING,
mSCRIPT_CLASS_INIT_DOCSTRING,
mSCRIPT_CLASS_INIT_INSTANCE_MEMBER,
mSCRIPT_CLASS_INIT_INHERIT,
mSCRIPT_CLASS_INIT_CAST_TO_MEMBER,
mSCRIPT_CLASS_INIT_INIT,
mSCRIPT_CLASS_INIT_DEINIT,
mSCRIPT_CLASS_INIT_GET,
mSCRIPT_CLASS_INIT_SET,
};
enum {
mSCRIPT_VALUE_FLAG_FREE_BUFFER = 1
};
struct mScriptType;
extern const struct mScriptType mSTVoid;
extern const struct mScriptType mSTSInt8;
extern const struct mScriptType mSTUInt8;
extern const struct mScriptType mSTSInt16;
extern const struct mScriptType mSTUInt16;
extern const struct mScriptType mSTSInt32;
extern const struct mScriptType mSTUInt32;
extern const struct mScriptType mSTFloat32;
extern const struct mScriptType mSTSInt64;
extern const struct mScriptType mSTUInt64;
extern const struct mScriptType mSTFloat64;
extern const struct mScriptType mSTString;
extern const struct mScriptType mSTCharPtr;
extern const struct mScriptType mSTList;
extern const struct mScriptType mSTTable;
extern const struct mScriptType mSTWrapper;
extern const struct mScriptType mSTWeakref;
extern const struct mScriptType mSTStringWrapper;
struct mScriptType;
struct mScriptValue {
const struct mScriptType* type;
int refs;
uint32_t flags;
union {
int32_t s32;
uint32_t u32;
float f32;
int64_t s64;
uint64_t u64;
double f64;
void* opaque;
const void* copaque;
struct mScriptString* string;
struct Table* table;
struct mScriptList* list;
} value;
};
struct mScriptTypeTuple {
size_t count;
const struct mScriptType* entries[mSCRIPT_PARAMS_MAX];
const char* names[mSCRIPT_PARAMS_MAX];
const struct mScriptValue* defaults;
bool variable;
};
struct mScriptTypeFunction {
struct mScriptTypeTuple parameters;
struct mScriptTypeTuple returnType;
// TODO: kwargs
};
struct mScriptClassMember {
const char* name;
const char* docstring;
const struct mScriptType* type;
size_t offset;
};
struct mScriptClassCastMember {
const struct mScriptType* type;
const char* member;
};
struct mScriptClassInitDetails {
enum mScriptClassInitType type;
union {
const char* comment;
const struct mScriptType* parent;
struct mScriptClassMember member;
struct mScriptClassCastMember castMember;
} info;
};
struct mScriptTypeClass {
bool init;
const struct mScriptClassInitDetails* details;
const struct mScriptType* parent;
const char* docstring;
struct Table instanceMembers;
struct Table castToMembers;
struct mScriptClassMember* alloc; // TODO
struct mScriptClassMember* free;
struct mScriptClassMember* get;
struct mScriptClassMember* set; // TODO
};
struct mScriptType {
enum mScriptTypeBase base : 8;
bool isConst;
size_t size;
const char* name;
union {
struct mScriptTypeTuple tuple;
struct mScriptTypeFunction function;
struct mScriptTypeClass* cls;
const struct mScriptType* type;
void* opaque;
} details;
const struct mScriptType* constType;
void (*alloc)(struct mScriptValue*);
void (*free)(struct mScriptValue*);
uint32_t (*hash)(const struct mScriptValue*);
bool (*equal)(const struct mScriptValue*, const struct mScriptValue*);
bool (*cast)(const struct mScriptValue*, const struct mScriptType*, struct mScriptValue*);
};
DECLARE_VECTOR(mScriptList, struct mScriptValue)
struct mScriptString {
size_t length; // Size of the string in code points
size_t size; // Size of the buffer in bytes, excluding NULL byte terminator
char* buffer; // UTF-8 representation of the string
};
struct mScriptFrame {
struct mScriptList arguments;
struct mScriptList returnValues;
// TODO: Exception/failure codes
};
struct mScriptFunction {
bool (*call)(struct mScriptFrame*, void* context);
void* context;
};
struct mScriptValue* mScriptValueAlloc(const struct mScriptType* type);
void mScriptValueRef(struct mScriptValue* val);
void mScriptValueDeref(struct mScriptValue* val);
void mScriptValueWrap(struct mScriptValue* val, struct mScriptValue* out);
struct mScriptValue* mScriptValueUnwrap(struct mScriptValue* val);
const struct mScriptValue* mScriptValueUnwrapConst(const struct mScriptValue* val);
struct mScriptValue* mScriptStringCreateEmpty(size_t size);
struct mScriptValue* mScriptStringCreateFromBytes(const void* string, size_t size);
struct mScriptValue* mScriptStringCreateFromUTF8(const char* string);
struct mScriptValue* mScriptStringCreateFromASCII(const char* string);
struct mScriptValue* mScriptValueCreateFromUInt(uint32_t value);
struct mScriptValue* mScriptValueCreateFromSInt(int32_t value);
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);
bool mScriptTableClear(struct mScriptValue* table);
size_t mScriptTableSize(struct mScriptValue* table);
bool mScriptTableIteratorStart(struct mScriptValue* table, struct TableIterator*);
bool mScriptTableIteratorNext(struct mScriptValue* table, struct TableIterator*);
struct mScriptValue* mScriptTableIteratorGetKey(struct mScriptValue* table, struct TableIterator*);
struct mScriptValue* mScriptTableIteratorGetValue(struct mScriptValue* table, struct TableIterator*);
bool mScriptTableIteratorLookup(struct mScriptValue* table, struct TableIterator*, struct mScriptValue* key);
void mScriptFrameInit(struct mScriptFrame* frame);
void mScriptFrameDeinit(struct mScriptFrame* frame);
void mScriptClassInit(struct mScriptTypeClass* cls);
void mScriptClassDeinit(struct mScriptTypeClass* cls);
bool mScriptObjectGet(struct mScriptValue* obj, const char* member, struct mScriptValue*);
bool mScriptObjectGetConst(const struct mScriptValue* obj, const char* member, struct mScriptValue*);
bool mScriptObjectSet(struct mScriptValue* obj, const char* member, struct mScriptValue*);
bool mScriptObjectCast(const struct mScriptValue* input, const struct mScriptType* type, struct mScriptValue* output) ;
void mScriptObjectFree(struct mScriptValue* obj);
bool mScriptPopS32(struct mScriptList* list, int32_t* out);
bool mScriptPopU32(struct mScriptList* list, uint32_t* out);
bool mScriptPopF32(struct mScriptList* list, float* out);
bool mScriptPopS64(struct mScriptList* list, int64_t* out);
bool mScriptPopU64(struct mScriptList* list, uint64_t* out);
bool mScriptPopF64(struct mScriptList* list, double* 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);
CXX_GUARD_END
#endif

515
res/scripts/pokemon.lua Normal file
View File

@ -0,0 +1,515 @@
local Game = {
new = function (self, game)
self.__index = self
setmetatable(game, self)
return game
end
}
function Game.getParty(game)
local party = {}
local monStart = game._party
local nameStart = game._partyNames
local otStart = game._partyOt
for i = 1, emu:read8(game._partyCount) do
party[i] = game:_readPartyMon(monStart, nameStart, otStart)
monStart = monStart + game._partyMonSize
if game._partyNames then
nameStart = nameStart + game._monNameLength + 1
end
if game._partyOt then
otStart = otStart + game._playerNameLength + 1
end
end
return party
end
function Game.toString(game, rawstring)
local string = ""
for _, char in ipairs({rawstring:byte(1, #rawstring)}) do
if char == game._terminator then
break
end
string = string..game._charmap[char]
end
return string
end
function Game.getSpeciesName(game, id)
if game._speciesIndex then
local index = game._index
if not index then
index = {}
for i = 0, 255 do
index[emu.memory.cart0:read8(game._speciesIndex + i)] = i
end
game._index = index
end
id = index[id]
end
local pointer = game._speciesNameTable + (game._speciesNameLength) * id
return game:toString(emu.memory.cart0:readRange(pointer, game._monNameLength))
end
local GBGameEn = Game:new{
_terminator=0x50,
_monNameLength=10,
_speciesNameLength=10,
_playerNameLength=10,
}
local GBAGameEn = Game:new{
_terminator=0xFF,
_monNameLength=10,
_speciesNameLength=11,
_playerNameLength=10,
}
local Generation1En = GBGameEn:new{
_boxMonSize=33,
_partyMonSize=44,
_readBoxMon=readBoxMonGen1,
_readPartyMon=readPartyMonGen1,
}
local Generation2En = GBGameEn:new{
_boxMonSize=32,
_partyMonSize=48,
_readBoxMon=readBoxMonGen2,
_readPartyMon=readPartyMonGen2,
}
local Generation3En = GBAGameEn:new{
_boxMonSize=80,
_partyMonSize=100,
_readBoxMon=readBoxMonGen3,
_readPartyMon=readPartyMonGen3,
}
GBGameEn._charmap = { [0]=
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", " ",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "(", ")", ":", ";", "[", "]",
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
"q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "é", "ʼd", "ʼl", "ʼs", "ʼt", "ʼv",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"'", "P\u{200d}k", "M\u{200d}n", "-", "ʼr", "ʼm", "?", "!", ".", "", "", "", "", "", "", "",
"$", "×", ".", "/", ",", "", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
}
GBAGameEn._charmap = { [0]=
" ", "À", "Á", "Â", "Ç", "È", "É", "Ê", "Ë", "Ì", "", "Î", "Ï", "Ò", "Ó", "Ô",
"Œ", "Ù", "Ú", "Û", "Ñ", "ß", "à", "á", "", "ç", "è", "é", "ê", "ë", "ì", "",
"î", "ï", "ò", "ó", "ô", "œ", "ù", "ú", "û", "ñ", "º", "ª", "<EFBFBD>", "&", "+", "",
"", "", "", "", "v", "=", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "¿", "¡", "P\u{200d}k", "M\u{200d}n", "P\u{200d}o", "K\u{200d}é", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "Í", "%", "(", ")", "", "",
"", "", "", "", "", "", "", "", "â", "", "", "", "", "", "", "í",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", ".", "-", "",
"", "", "", "", "", "", "", "$", ",", "×", "/", "A", "B", "C", "D", "E",
"F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U",
"V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "",
":", "Ä", "Ö", "Ü", "ä", "ö", "ü", "", "", "", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", ""
}
function _read16BE(emu, address)
return (emu:read8(address) << 8) | emu:read8(address + 1)
end
function Generation1En._readBoxMon(game, address, nameAddress, otAddress)
local mon = {}
mon.species = emu.memory.cart0:read8(game._speciesIndex + emu:read8(address + 0) - 1)
mon.hp = _read16BE(emu, address + 1)
mon.level = emu:read8(address + 3)
mon.status = emu:read8(address + 4)
mon.type1 = emu:read8(address + 5)
mon.type2 = emu:read8(address + 6)
mon.catchRate = emu:read8(address + 7)
mon.moves = {
emu:read8(address + 8),
emu:read8(address + 9),
emu:read8(address + 10),
emu:read8(address + 11),
}
mon.otId = _read16BE(emu, address + 12)
mon.experience = (_read16BE(emu, address + 14) << 8)| emu:read8(address + 16)
mon.hpEV = _read16BE(emu, address + 17)
mon.attackEV = _read16BE(emu, address + 19)
mon.defenseEV = _read16BE(emu, address + 21)
mon.speedEV = _read16BE(emu, address + 23)
mon.spAttackEV = _read16BE(emu, address + 25)
mon.spDefenseEV = mon.spAttackEv
local iv = _read16BE(emu, address + 27)
mon.attackIV = (iv >> 4) & 0xF
mon.defenseIV = iv & 0xF
mon.speedIV = (iv >> 12) & 0xF
mon.spAttackIV = (iv >> 8) & 0xF
mon.spDefenseIV = mon.spAttackIV
mon.pp = {
emu:read8(address + 28),
emu:read8(address + 29),
emu:read8(address + 30),
emu:read8(address + 31),
}
mon.nickname = game:toString(emu:readRange(nameAddress, game._monNameLength))
mon.otName = game:toString(emu:readRange(otAddress, game._playerNameLength))
return mon
end
function Generation1En._readPartyMon(game, address, nameAddress, otAddress)
local mon = game:_readBoxMon(address, nameAddress, otAddress)
mon.level = emu:read8(address + 33)
mon.maxHP = _read16BE(emu, address + 34)
mon.attack = _read16BE(emu, address + 36)
mon.defense = _read16BE(emu, address + 38)
mon.speed = _read16BE(emu, address + 40)
mon.spAttack = _read16BE(emu, address + 42)
mon.spDefense = mon.spAttack
return mon
end
function Generation2En._readBoxMon(game, address, nameAddress, otAddress)
local mon = {}
mon.species = emu:read8(address + 0)
mon.item = emu:read8(address + 1)
mon.moves = {
emu:read8(address + 2),
emu:read8(address + 3),
emu:read8(address + 4),
emu:read8(address + 5),
}
mon.otId = _read16BE(emu, address + 6)
mon.experience = (_read16BE(emu, address + 8) << 8)| emu:read8(address + 10)
mon.hpEV = _read16BE(emu, address + 11)
mon.attackEV = _read16BE(emu, address + 13)
mon.defenseEV = _read16BE(emu, address + 15)
mon.speedEV = _read16BE(emu, address + 17)
mon.spAttackEV = _read16BE(emu, address + 19)
mon.spDefenseEV = mon.spAttackEv
local iv = _read16BE(emu, address + 21)
mon.attackIV = (iv >> 4) & 0xF
mon.defenseIV = iv & 0xF
mon.speedIV = (iv >> 12) & 0xF
mon.spAttackIV = (iv >> 8) & 0xF
mon.spDefenseIV = mon.spAttackIV
mon.pp = {
emu:read8(address + 23),
emu:read8(address + 24),
emu:read8(address + 25),
emu:read8(address + 26),
}
mon.friendship = emu:read8(address + 27)
mon.pokerus = emu:read8(address + 28)
local caughtData = _read16BE(emu, address + 29)
mon.metLocation = (caughtData >> 8) & 0x7F
mon.metLevel = caughtData & 0x1F
mon.level = emu:read8(address + 31)
mon.nickname = game:toString(emu:readRange(nameAddress, game._monNameLength))
mon.otName = game:toString(emu:readRange(otAddress, game._playerNameLength))
return mon
end
function Generation2En._readPartyMon(game, address, nameAddress, otAddress)
local mon = game:_readBoxMon(address, nameAddress, otAddress)
mon.status = emu:read8(address + 32)
mon.hp = _read16BE(emu, address + 34)
mon.maxHP = _read16BE(emu, address + 36)
mon.attack = _read16BE(emu, address + 38)
mon.defense = _read16BE(emu, address + 40)
mon.speed = _read16BE(emu, address + 42)
mon.spAttack = _read16BE(emu, address + 44)
mon.spDefense = _read16BE(emu, address + 46)
return mon
end
function Generation3En._readBoxMon(game, address)
local mon = {}
mon.personality = emu:read32(address + 0)
mon.otId = emu:read32(address + 4)
mon.nickname = game:toString(emu:readRange(address + 8, game._monNameLength))
mon.language = emu:read8(address + 18)
local flags = emu:read8(address + 19)
mon.isBadEgg = flags & 1
mon.hasSpecies = (flags >> 1) & 1
mon.isEgg = (flags >> 2) & 1
mon.otName = game:toString(emu:readRange(address + 20, game._playerNameLength))
mon.markings = emu:read8(address + 27)
local key = mon.otId ~ mon.personality
local substructSelector = {
[ 0] = {0, 1, 2, 3},
[ 1] = {0, 1, 3, 2},
[ 2] = {0, 2, 1, 3},
[ 3] = {0, 3, 1, 2},
[ 4] = {0, 2, 3, 1},
[ 5] = {0, 3, 2, 1},
[ 6] = {1, 0, 2, 3},
[ 7] = {1, 0, 3, 2},
[ 8] = {2, 0, 1, 3},
[ 9] = {3, 0, 1, 2},
[10] = {2, 0, 3, 1},
[11] = {3, 0, 2, 1},
[12] = {1, 2, 0, 3},
[13] = {1, 3, 0, 2},
[14] = {2, 1, 0, 3},
[15] = {3, 1, 0, 2},
[16] = {2, 3, 0, 1},
[17] = {3, 2, 0, 1},
[18] = {1, 2, 3, 0},
[19] = {1, 3, 2, 0},
[20] = {2, 1, 3, 0},
[21] = {3, 1, 2, 0},
[22] = {2, 3, 1, 0},
[23] = {3, 2, 1, 0},
}
local pSel = substructSelector[mon.personality % 24]
local ss0 = {}
local ss1 = {}
local ss2 = {}
local ss3 = {}
for i = 0, 2 do
ss0[i] = emu:read32(address + 32 + pSel[1] * 12 + i * 4) ~ key
ss1[i] = emu:read32(address + 32 + pSel[2] * 12 + i * 4) ~ key
ss2[i] = emu:read32(address + 32 + pSel[3] * 12 + i * 4) ~ key
ss3[i] = emu:read32(address + 32 + pSel[4] * 12 + i * 4) ~ key
end
mon.species = ss0[0] & 0xFFFF
mon.heldItem = ss0[0] >> 16
mon.experience = ss0[1]
mon.ppBonuses = ss0[2] & 0xFF
mon.friendship = (ss0[2] >> 8) & 0xFF
mon.moves = {
ss1[0] & 0xFFFF,
ss1[0] >> 16,
ss1[1] & 0xFFFF,
ss1[1] >> 16
}
mon.pp = {
ss1[2] & 0xFF,
(ss1[2] >> 8) & 0xFF,
(ss1[2] >> 16) & 0xFF,
ss1[2] >> 24
}
mon.hpEV = ss2[0] & 0xFF
mon.attackEV = (ss2[0] >> 8) & 0xFF
mon.defenseEV = (ss2[0] >> 16) & 0xFF
mon.speedEV = ss2[0] >> 24
mon.spAttackEV = ss2[1] & 0xFF
mon.spDefenseEV = (ss2[1] >> 8) & 0xFF
mon.cool = (ss2[1] >> 16) & 0xFF
mon.beauty = ss2[1] >> 24
mon.cute = ss2[2] & 0xFF
mon.smart = (ss2[2] >> 8) & 0xFF
mon.tough = (ss2[2] >> 16) & 0xFF
mon.sheen = ss2[2] >> 24
mon.pokerus = ss3[0] & 0xFF
mon.metLocation = (ss3[0] >> 8) & 0xFF
flags = ss3[0] >> 16
mon.metLevel = flags & 0x7F
mon.metGame = (flags >> 7) & 0xF
mon.pokeball = (flags >> 11) & 0xF
mon.otGender = (flags >> 15) & 0x1
flags = ss3[1]
mon.hpIV = flags & 0x1F
mon.attackIV = (flags >> 5) & 0x1F
mon.defenseIV = (flags >> 10) & 0x1F
mon.speedIV = (flags >> 15) & 0x1F
mon.spAttackIV = (flags >> 20) & 0x1F
mon.spDefenseIV = (flags >> 25) & 0x1F
-- Bit 30 is another "isEgg" bit
mon.altAbility = (flags >> 31) & 1
flags = ss3[2]
mon.coolRibbon = flags & 7
mon.beautyRibbon = (flags >> 3) & 7
mon.cuteRibbon = (flags >> 6) & 7
mon.smartRibbon = (flags >> 9) & 7
mon.toughRibbon = (flags >> 12) & 7
mon.championRibbon = (flags >> 15) & 1
mon.winningRibbon = (flags >> 16) & 1
mon.victoryRibbon = (flags >> 17) & 1
mon.artistRibbon = (flags >> 18) & 1
mon.effortRibbon = (flags >> 19) & 1
mon.marineRibbon = (flags >> 20) & 1
mon.landRibbon = (flags >> 21) & 1
mon.skyRibbon = (flags >> 22) & 1
mon.countryRibbon = (flags >> 23) & 1
mon.nationalRibbon = (flags >> 24) & 1
mon.earthRibbon = (flags >> 25) & 1
mon.worldRibbon = (flags >> 26) & 1
mon.eventLegal = (flags >> 27) & 0x1F
return mon
end
function Generation3En._readPartyMon(game, address)
local mon = game:_readBoxMon(address)
mon.status = emu:read32(address + 80)
mon.level = emu:read8(address + 84)
mon.mail = emu:read32(address + 85)
mon.hp = emu:read16(address + 86)
mon.maxHP = emu:read16(address + 88)
mon.attack = emu:read16(address + 90)
mon.defense = emu:read16(address + 92)
mon.speed = emu:read16(address + 94)
mon.spAttack = emu:read16(address + 96)
mon.spDefense = emu:read16(address + 98)
return mon
end
local gameRBEn = Generation1En:new{
name="Red/Blue (USA)",
_party=0xd16b,
_partyCount=0xd163,
_partyNames=0xd2b5,
_partyOt=0xd273,
_speciesNameTable=0x1c21e,
_speciesIndex=0x41024,
}
local gameYellowEn = Generation1En:new{
name="Yellow (USA)",
_party=0xd16a,
_partyCount=0xd162,
_partyNames=0xd2b4,
_partyOt=0xd272,
_speciesNameTable=0xe8000,
_speciesIndex=0x410b1,
}
local gameGSEn = Generation2En:new{
name="Gold/Silver (USA)",
_party=0xda2a,
_partyCount=0xda22,
_partyNames=0xdb8c,
_partyOt=0xdb4a,
_speciesNameTable=0x1b0b6a,
}
local gameCrystalEn = Generation2En:new{
name="Crystal (USA)",
_party=0xdcdf,
_partyCount=0xdcd7,
_partyNames=0xde41,
_partyOt=0xddff,
_speciesNameTable=0x5337a,
}
local gameRubyEn = Generation3En:new{
name="Ruby (USA)",
_party=0x3004360,
_partyCount=0x3004350,
_speciesNameTable=0x1f716c,
}
local gameSapphireEn = Generation3En:new{
name="Sapphire (USA)",
_party=0x3004360,
_partyCount=0x3004350,
_speciesNameTable=0x1f70fc,
}
local gameEmeraldEn = Generation3En:new{
name="Emerald (USA)",
_party=0x20244ec,
_partyCount=0x20244e9,
_speciesNameTable=0x3185c8,
}
local gameFireRedEn = Generation3En:new{
name="FireRed (USA)",
_party=0x2024284,
_partyCount=0x2024029,
_speciesNameTable=0x245ee0,
}
local gameFireRedEnR1 = gameFireRedEn:new{
name="FireRed (USA) (Rev 1)",
_speciesNameTable=0x245f50,
}
local gameLeafGreenEn = Generation3En:new{
name="LeafGreen (USA)",
_party=0x2024284,
_partyCount=0x2024029,
_speciesNameTable=0x245ebc,
}
local gameLeafGreenEnR1 = gameLeafGreenEn:new{
name="LeafGreen (USA)",
_party=0x2024284,
_partyCount=0x2024029,
_speciesNameTable=0x245f2c,
}
gameCodes = {
["DMG-AAUE"]=gameGSEn, -- Gold
["DMG-AAXE"]=gameGSEn, -- Silver
["CGB-BYTE"]=gameCrystalEn,
["AGB-AXVE"]=gameRubyEn,
["AGB-AXPE"]=gameSapphireEn,
["AGB-BPEE"]=gameEmeraldEn,
["AGB-BPRE"]=gameFireRedEn,
["AGB-BPGE"]=gameLeafGreenEn,
}
-- These versions have slight differences and/or cannot be uniquely
-- identified by their in-header game codes, so fall back on a CRC32
gameCrc32 = {
[0x9f7fdd53] = gameRBEn, -- Red
[0xd6da8a1a] = gameRBEn, -- Blue
[0x7d527d62] = gameYellowEn,
[0x3358e30a] = gameCrystal, -- Crystal rev 1
[0x84ee4776] = gameFireRedEnR1,
[0xdaffecec] = gameLeafGreenEnR1,
}
function printPartyStatus(game, buffer)
buffer:clear()
for _, mon in ipairs(game:getParty()) do
buffer:print(string.format("%-10s (Lv%3i %10s): %3i/%3i\n",
mon.nickname,
mon.level,
game:getSpeciesName(mon.species),
mon.hp,
mon.maxHP))
end
end
function detectGame()
local checksum = 0
for i, v in ipairs({emu:checksum(C.CHECKSUM.CRC32):byte(1, 4)}) do
checksum = checksum * 256 + v
end
game = gameCrc32[checksum]
if not game then
game = gameCodes[emu:getGameCode()]
end
if not game then
console:error("Unknown game!")
else
console:log("Found game: " .. game.name)
end
end
callbacks:add("start", detectGame)
if emu then
detectGame()
end

View File

@ -14,7 +14,6 @@ set(SOURCE_FILES
map-cache.c
mem-search.c
rewind.c
scripting.c
serialize.c
sync.c
thread.c
@ -24,8 +23,18 @@ set(SOURCE_FILES
set(TEST_FILES
test/core.c)
if(ENABLE_SCRIPTING)
list(APPEND SOURCE_FILES
scripting.c)
if(USE_LUA)
list(APPEND TEST_FILES
test/scripting.c)
endif()
endif()
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)

View File

@ -308,6 +308,9 @@ struct VFile* mCoreGetState(struct mCore* core, int slot, bool write) {
if (!core->dirs.state) {
return NULL;
}
if (slot < 0) {
return NULL;
}
char name[PATH_MAX + 14]; // Quash warning
snprintf(name, sizeof(name), "%s.ss%i", core->dirs.baseName, slot);
return core->dirs.state->openFile(core->dirs.state, name, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
@ -321,10 +324,6 @@ void mCoreDeleteState(struct mCore* core, int slot) {
void mCoreTakeScreenshot(struct mCore* core) {
#ifdef USE_PNG
size_t stride;
const void* pixels = 0;
unsigned width, height;
core->desiredVideoDimensions(core, &width, &height);
struct VFile* vf;
#ifndef PSP2
vf = VDirFindNextAvailable(core->dirs.screenshot, core->dirs.baseName, "-", ".png", O_CREAT | O_TRUNC | O_WRONLY);
@ -333,11 +332,7 @@ void mCoreTakeScreenshot(struct mCore* core) {
#endif
bool success = false;
if (vf) {
core->getPixels(core, &pixels, &stride);
png_structp png = PNGWriteOpen(vf);
png_infop info = PNGWriteHeader(png, width, height);
success = PNGWritePixels(png, width, height, stride, pixels);
PNGWriteClose(png, info);
success = mCoreTakeScreenshotVF(core, vf);
#ifdef PSP2
void* data = vf->map(vf, 0, 0);
PhotoExportParam param = {
@ -362,6 +357,23 @@ void mCoreTakeScreenshot(struct mCore* core) {
}
#endif
bool mCoreTakeScreenshotVF(struct mCore* core, struct VFile* vf) {
#ifdef USE_PNG
size_t stride;
const void* pixels = 0;
unsigned width, height;
core->desiredVideoDimensions(core, &width, &height);
core->getPixels(core, &pixels, &stride);
png_structp png = PNGWriteOpen(vf);
png_infop info = PNGWriteHeader(png, width, height);
bool success = PNGWritePixels(png, width, height, stride, pixels);
PNGWriteClose(png, info);
return success;
#else
return false;
#endif
}
void mCoreInitConfig(struct mCore* core, const char* port) {
mCoreConfigInit(&core->config, port);
}

View File

@ -80,6 +80,14 @@ void mLog(int category, enum mLogLevel level, const char* format, ...) {
va_end(args);
}
void mLogExplicit(struct mLogger* context, int category, enum mLogLevel level, const char* format, ...) {
va_list args;
va_start(args, format);
if (!context->filter || mLogFilterTest(context->filter, category, level)) {
context->log(context, category, level, format, args);
}
}
void mLogFilterInit(struct mLogFilter* filter) {
HashTableInit(&filter->categories, 8, NULL);
TableInit(&filter->levels, 8, NULL);

View File

@ -1,13 +1,18 @@
/* Copyright (c) 2013-2017 Jeffrey Pfau
/* Copyright (c) 2013-2022 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/core/scripting.h>
#include <mgba/core/core.h>
#include <mgba/core/serialize.h>
#include <mgba/script/context.h>
#include <mgba-util/table.h>
#include <mgba-util/vfs.h>
mLOG_DEFINE_CATEGORY(SCRIPT, "Scripting", "script");
struct mScriptBridge {
struct Table engines;
struct mDebugger* debugger;
@ -91,8 +96,16 @@ void mScriptBridgeInstallEngine(struct mScriptBridge* sb, struct mScriptEngine*
#ifdef USE_DEBUGGERS
void mScriptBridgeSetDebugger(struct mScriptBridge* sb, struct mDebugger* debugger) {
if (sb->debugger == debugger) {
return;
}
if (sb->debugger) {
sb->debugger->bridge = NULL;
}
sb->debugger = debugger;
debugger->bridge = sb;
if (debugger) {
debugger->bridge = sb;
}
}
struct mDebugger* mScriptBridgeGetDebugger(struct mScriptBridge* sb) {
@ -136,3 +149,636 @@ bool mScriptBridgeLookupSymbol(struct mScriptBridge* sb, const char* name, int32
HashTableEnumerate(&sb->engines, _seLookupSymbol, &info);
return info.success;
}
struct mScriptMemoryDomain {
struct mCore* core;
struct mCoreMemoryBlock block;
};
struct mScriptCoreAdapter {
struct mCore* core;
struct mScriptContext* context;
struct mScriptValue memory;
};
struct mScriptConsole {
struct mLogger* logger;
mScriptContextBufferFactory textBufferFactory;
void* textBufferContext;
};
#define CALCULATE_SEGMENT_INFO \
uint32_t segmentSize = adapter->block.end - adapter->block.start; \
uint32_t segmentStart = adapter->block.segmentStart - adapter->block.start; \
if (adapter->block.segmentStart) { \
segmentSize -= segmentStart; \
}
#define CALCULATE_SEGMENT_ADDRESS \
uint32_t segmentAddress = address % segmentSize; \
int segment = address / segmentSize; \
segmentAddress += adapter->block.start; \
if (adapter->block.segmentStart && segment) { \
segmentAddress += segmentStart; \
}
static uint32_t mScriptMemoryDomainRead8(struct mScriptMemoryDomain* adapter, uint32_t address) {
CALCULATE_SEGMENT_INFO;
CALCULATE_SEGMENT_ADDRESS;
return adapter->core->rawRead8(adapter->core, segmentAddress, segment);
}
static uint32_t mScriptMemoryDomainRead16(struct mScriptMemoryDomain* adapter, uint32_t address) {
CALCULATE_SEGMENT_INFO;
CALCULATE_SEGMENT_ADDRESS;
return adapter->core->rawRead16(adapter->core, segmentAddress, segment);
}
static uint32_t mScriptMemoryDomainRead32(struct mScriptMemoryDomain* adapter, uint32_t address) {
CALCULATE_SEGMENT_INFO;
CALCULATE_SEGMENT_ADDRESS;
return adapter->core->rawRead32(adapter->core, segmentAddress, segment);
}
static struct mScriptValue* mScriptMemoryDomainReadRange(struct mScriptMemoryDomain* adapter, uint32_t address, uint32_t length) {
CALCULATE_SEGMENT_INFO;
struct mScriptValue* value = mScriptStringCreateEmpty(length);
char* buffer = value->value.string->buffer;
uint32_t i;
for (i = 0; i < length; ++i, ++address) {
CALCULATE_SEGMENT_ADDRESS;
buffer[i] = adapter->core->rawRead8(adapter->core, segmentAddress, segment);
}
return value;
}
static void mScriptMemoryDomainWrite8(struct mScriptMemoryDomain* adapter, uint32_t address, uint8_t value) {
CALCULATE_SEGMENT_INFO;
CALCULATE_SEGMENT_ADDRESS;
adapter->core->rawWrite8(adapter->core, address, segmentAddress, value);
}
static void mScriptMemoryDomainWrite16(struct mScriptMemoryDomain* adapter, uint32_t address, uint16_t value) {
CALCULATE_SEGMENT_INFO;
CALCULATE_SEGMENT_ADDRESS;
adapter->core->rawWrite16(adapter->core, address, segmentAddress, value);
}
static void mScriptMemoryDomainWrite32(struct mScriptMemoryDomain* adapter, uint32_t address, uint32_t value) {
CALCULATE_SEGMENT_INFO;
CALCULATE_SEGMENT_ADDRESS;
adapter->core->rawWrite32(adapter->core, address, segmentAddress, value);
}
static uint32_t mScriptMemoryDomainBase(struct mScriptMemoryDomain* adapter) {
return adapter->block.start;
}
static uint32_t mScriptMemoryDomainEnd(struct mScriptMemoryDomain* adapter) {
return adapter->block.end;
}
static uint32_t mScriptMemoryDomainSize(struct mScriptMemoryDomain* adapter) {
return adapter->block.size;
}
static struct mScriptValue* mScriptMemoryDomainName(struct mScriptMemoryDomain* adapter) {
return mScriptStringCreateFromUTF8(adapter->block.shortName);
}
mSCRIPT_DECLARE_STRUCT(mScriptMemoryDomain);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptMemoryDomain, U32, read8, mScriptMemoryDomainRead8, 1, U32, address);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptMemoryDomain, U32, read16, mScriptMemoryDomainRead16, 1, U32, address);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptMemoryDomain, U32, read32, mScriptMemoryDomainRead32, 1, U32, address);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptMemoryDomain, WSTR, readRange, mScriptMemoryDomainReadRange, 2, U32, address, U32, length);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptMemoryDomain, write8, mScriptMemoryDomainWrite8, 2, U32, address, U8, value);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptMemoryDomain, write16, mScriptMemoryDomainWrite16, 2, U32, address, U16, value);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptMemoryDomain, write32, mScriptMemoryDomainWrite32, 2, U32, address, U32, value);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptMemoryDomain, U32, base, mScriptMemoryDomainBase, 0);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptMemoryDomain, U32, bound, mScriptMemoryDomainEnd, 0);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptMemoryDomain, U32, size, mScriptMemoryDomainSize, 0);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptMemoryDomain, WSTR, name, mScriptMemoryDomainName, 0);
mSCRIPT_DEFINE_STRUCT(mScriptMemoryDomain)
mSCRIPT_DEFINE_CLASS_DOCSTRING(
"An object used for access directly to a memory domain, e.g. the cartridge, "
"instead of through a whole address space, as with the functions directly on struct::mCore."
)
mSCRIPT_DEFINE_DOCSTRING("Read an 8-bit value from the given offset")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptMemoryDomain, read8)
mSCRIPT_DEFINE_DOCSTRING("Read a 16-bit value from the given offset")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptMemoryDomain, read16)
mSCRIPT_DEFINE_DOCSTRING("Read a 32-bit value from the given offset")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptMemoryDomain, read32)
mSCRIPT_DEFINE_DOCSTRING("Read byte range from the given offset")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptMemoryDomain, readRange)
mSCRIPT_DEFINE_DOCSTRING("Write an 8-bit value from the given offset")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptMemoryDomain, write8)
mSCRIPT_DEFINE_DOCSTRING("Write a 16-bit value from the given offset")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptMemoryDomain, write16)
mSCRIPT_DEFINE_DOCSTRING("Write a 32-bit value from the given offset")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptMemoryDomain, write32)
mSCRIPT_DEFINE_DOCSTRING("Get the address of the base of this memory domain")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptMemoryDomain, base)
mSCRIPT_DEFINE_DOCSTRING("Get the address of the end bound of this memory domain. Note that this address is not in the domain itself, and is the address of the first byte past it")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptMemoryDomain, bound)
mSCRIPT_DEFINE_DOCSTRING("Get the size of this memory domain in bytes")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptMemoryDomain, size)
mSCRIPT_DEFINE_DOCSTRING("Get a short, human-readable name for this memory domain")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptMemoryDomain, name)
mSCRIPT_DEFINE_END;
static struct mScriptValue* _mScriptCoreGetGameTitle(const struct mCore* core) {
char title[32] = {0};
core->getGameTitle(core, title);
return mScriptStringCreateFromASCII(title);
}
static struct mScriptValue* _mScriptCoreGetGameCode(const struct mCore* core) {
char code[16] = {0};
core->getGameCode(core, code);
return mScriptStringCreateFromASCII(code);
}
static struct mScriptValue* _mScriptCoreChecksum(const struct mCore* core, int t) {
enum mCoreChecksumType type = (enum mCoreChecksumType) t;
size_t size = 0;
switch (type) {
case mCHECKSUM_CRC32:
size = 4;
break;
}
if (!size) {
return NULL;
}
void* data = calloc(1, size);
core->checksum(core, data, type);
if (type == mCHECKSUM_CRC32) {
// This checksum is endian-dependent...let's just make it big endian for Lua
uint32_t* crc = data;
STORE_32BE(*crc, 0, crc);
}
struct mScriptValue* ret = mScriptStringCreateFromBytes(data, size);
free(data);
return ret;
}
static void _mScriptCoreAddKey(struct mCore* core, int32_t key) {
core->addKeys(core, 1 << key);
}
static void _mScriptCoreClearKey(struct mCore* core, int32_t key) {
core->clearKeys(core, 1 << key);
}
static int32_t _mScriptCoreGetKey(struct mCore* core, int32_t key) {
return (core->getKeys(core) >> key) & 1;
}
static struct mScriptValue* _mScriptCoreReadRange(struct mCore* core, uint32_t address, uint32_t length) {
struct mScriptValue* value = mScriptStringCreateEmpty(length);
char* buffer = value->value.string->buffer;
uint32_t i;
for (i = 0; i < length; ++i, ++address) {
buffer[i] = core->busRead8(core, address);
}
return value;
}
static struct mScriptValue* _mScriptCoreReadRegister(const struct mCore* core, const char* regName) {
int32_t out;
if (!core->readRegister(core, regName, &out)) {
return NULL;
}
struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32);
value->value.s32 = out;
return value;
}
static void _mScriptCoreWriteRegister(struct mCore* core, const char* regName, int32_t in) {
core->writeRegister(core, regName, &in);
}
static struct mScriptValue* _mScriptCoreSaveState(struct mCore* core, int32_t flags) {
struct VFile* vf = VFileMemChunk(NULL, 0);
if (!mCoreSaveStateNamed(core, vf, flags)) {
vf->close(vf);
return NULL;
}
void* buffer = vf->map(vf, vf->size(vf), MAP_READ);
struct mScriptValue* value = mScriptStringCreateFromBytes(buffer, vf->size(vf));
vf->close(vf);
return value;
}
static int32_t _mScriptCoreLoadState(struct mCore* core, struct mScriptString* buffer, int32_t flags) {
struct VFile* vf = VFileFromConstMemory(buffer->buffer, buffer->size);
int ret = mCoreLoadStateNamed(core, vf, flags);
vf->close(vf);
return ret;
}
static void _mScriptCoreTakeScreenshot(struct mCore* core, const char* filename) {
if (filename) {
struct VFile* vf = VFileOpen(filename, O_WRONLY | O_CREAT | O_TRUNC);
if (!vf) {
return;
}
mCoreTakeScreenshotVF(core, vf);
vf->close(vf);
} else {
mCoreTakeScreenshot(core);
}
}
// Info functions
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mCore, S32, platform, 0);
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mCore, U32, frameCounter, 0);
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mCore, S32, frameCycles, 0);
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mCore, S32, frequency, 0);
mSCRIPT_DECLARE_STRUCT_C_METHOD(mCore, WSTR, getGameTitle, _mScriptCoreGetGameTitle, 0);
mSCRIPT_DECLARE_STRUCT_C_METHOD(mCore, WSTR, getGameCode, _mScriptCoreGetGameCode, 0);
mSCRIPT_DECLARE_STRUCT_C_METHOD_WITH_DEFAULTS(mCore, WSTR, checksum, _mScriptCoreChecksum, 1, S32, type);
// Run functions
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mCore, reset, 0);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mCore, runFrame, 0);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mCore, step, 0);
// Key functions
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mCore, setKeys, 1, U32, keys);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mCore, addKeys, 1, U32, keys);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mCore, clearKeys, 1, U32, keys);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mCore, addKey, _mScriptCoreAddKey, 1, S32, key);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mCore, clearKey, _mScriptCoreClearKey, 1, S32, key);
mSCRIPT_DECLARE_STRUCT_METHOD(mCore, S32, getKey, _mScriptCoreGetKey, 1, S32, key);
mSCRIPT_DECLARE_STRUCT_D_METHOD(mCore, U32, getKeys, 0);
// Memory functions
mSCRIPT_DECLARE_STRUCT_D_METHOD(mCore, U32, busRead8, 1, U32, address);
mSCRIPT_DECLARE_STRUCT_D_METHOD(mCore, U32, busRead16, 1, U32, address);
mSCRIPT_DECLARE_STRUCT_D_METHOD(mCore, U32, busRead32, 1, U32, address);
mSCRIPT_DECLARE_STRUCT_METHOD(mCore, WSTR, readRange, _mScriptCoreReadRange, 2, U32, address, U32, length);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mCore, busWrite8, 2, U32, address, U8, value);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mCore, busWrite16, 2, U32, address, U16, value);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mCore, busWrite32, 2, U32, address, U32, value);
// Register functions
mSCRIPT_DECLARE_STRUCT_METHOD(mCore, WSTR, readRegister, _mScriptCoreReadRegister, 1, CHARP, regName);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mCore, writeRegister, _mScriptCoreWriteRegister, 2, CHARP, regName, S32, value);
// Savestate functions
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, S32, saveStateSlot, mCoreSaveState, 2, S32, slot, S32, flags);
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, WSTR, saveStateBuffer, _mScriptCoreSaveState, 1, S32, flags);
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, S32, loadStateSlot, mCoreLoadState, 2, S32, slot, S32, flags);
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, S32, loadStateBuffer, _mScriptCoreLoadState, 2, STR, buffer, S32, flags);
// Miscellaneous functions
mSCRIPT_DECLARE_STRUCT_VOID_METHOD_WITH_DEFAULTS(mCore, screenshot, _mScriptCoreTakeScreenshot, 1, CHARP, filename);
mSCRIPT_DEFINE_STRUCT(mCore)
mSCRIPT_DEFINE_CLASS_DOCSTRING(
"An instance of an emulator core."
)
mSCRIPT_DEFINE_DOCSTRING("Get which platform is being emulated. See C.PLATFORM for possible values")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, platform)
mSCRIPT_DEFINE_DOCSTRING("Get the number of the current frame")
mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(mCore, currentFrame, frameCounter)
mSCRIPT_DEFINE_DOCSTRING("Get the number of cycles per frame")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, frameCycles)
mSCRIPT_DEFINE_DOCSTRING("Get the number of cycles per second")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, frequency)
mSCRIPT_DEFINE_DOCSTRING("Get the checksum of the loaded ROM")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, checksum)
mSCRIPT_DEFINE_DOCSTRING("Get internal title of the game from the ROM header")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, getGameTitle)
mSCRIPT_DEFINE_DOCSTRING("Get internal product code for the game from the ROM header, if available")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, getGameCode)
mSCRIPT_DEFINE_DOCSTRING("Reset the emulation. This does not invoke the **reset** callback")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, reset)
mSCRIPT_DEFINE_DOCSTRING("Run until the next frame")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, runFrame)
mSCRIPT_DEFINE_DOCSTRING("Run a single instruction")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, step)
mSCRIPT_DEFINE_DOCSTRING("Set the currently active key list")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, setKeys)
mSCRIPT_DEFINE_DOCSTRING("Add a single key to the currently active key list")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, addKey)
mSCRIPT_DEFINE_DOCSTRING("Add a bitmask of keys to the currently active key list")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, addKeys)
mSCRIPT_DEFINE_DOCSTRING("Remove a single key from the currently active key list")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, clearKey)
mSCRIPT_DEFINE_DOCSTRING("Remove a bitmask of keys from the currently active key list")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, clearKeys)
mSCRIPT_DEFINE_DOCSTRING("Get the active state of a given key")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, getKey)
mSCRIPT_DEFINE_DOCSTRING("Get the currently active keys as a bitmask")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, getKeys)
mSCRIPT_DEFINE_DOCSTRING("Read an 8-bit value from the given bus address")
mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(mCore, read8, busRead8)
mSCRIPT_DEFINE_DOCSTRING("Read a 16-bit value from the given bus address")
mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(mCore, read16, busRead16)
mSCRIPT_DEFINE_DOCSTRING("Read a 32-bit value from the given bus address")
mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(mCore, read32, busRead32)
mSCRIPT_DEFINE_DOCSTRING("Read byte range from the given offset")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, readRange)
mSCRIPT_DEFINE_DOCSTRING("Write an 8-bit value from the given bus address")
mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(mCore, write8, busWrite8)
mSCRIPT_DEFINE_DOCSTRING("Write a 16-bit value from the given bus address")
mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(mCore, write16, busWrite16)
mSCRIPT_DEFINE_DOCSTRING("Write a 32-bit value from the given bus address")
mSCRIPT_DEFINE_STRUCT_METHOD_NAMED(mCore, write32, busWrite32)
mSCRIPT_DEFINE_DOCSTRING("Read the value of the register with the given name")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, readRegister)
mSCRIPT_DEFINE_DOCSTRING("Write the value of the register with the given name")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, writeRegister)
mSCRIPT_DEFINE_DOCSTRING("Save state to the slot number. See C.SAVESTATE for possible values for `flags`")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, saveStateSlot)
mSCRIPT_DEFINE_DOCSTRING("Save state and return as a buffer. See C.SAVESTATE for possible values for `flags`")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, saveStateBuffer)
mSCRIPT_DEFINE_DOCSTRING("Load state from the slot number. See C.SAVESTATE for possible values for `flags`")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, loadStateSlot)
mSCRIPT_DEFINE_DOCSTRING("Load state a buffer. See C.SAVESTATE for possible values for `flags`")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, loadStateBuffer)
mSCRIPT_DEFINE_DOCSTRING("Save a screenshot")
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, screenshot)
mSCRIPT_DEFINE_END;
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, checksum)
mSCRIPT_MAKE_S32(mCHECKSUM_CRC32)
mSCRIPT_DEFINE_DEFAULTS_END;
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, saveStateSlot)
mSCRIPT_NO_DEFAULT,
mSCRIPT_MAKE_S32(SAVESTATE_ALL)
mSCRIPT_DEFINE_DEFAULTS_END;
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, loadStateSlot)
mSCRIPT_NO_DEFAULT,
mSCRIPT_MAKE_S32(SAVESTATE_ALL & ~SAVESTATE_SAVEDATA)
mSCRIPT_DEFINE_DEFAULTS_END;
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, saveStateBuffer)
mSCRIPT_MAKE_S32(SAVESTATE_ALL)
mSCRIPT_DEFINE_DEFAULTS_END;
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, loadStateBuffer)
mSCRIPT_NO_DEFAULT,
mSCRIPT_MAKE_S32(SAVESTATE_ALL & ~SAVESTATE_SAVEDATA)
mSCRIPT_DEFINE_DEFAULTS_END;
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, screenshot)
mSCRIPT_MAKE_CHARP(NULL)
mSCRIPT_DEFINE_DEFAULTS_END;
static void _clearMemoryMap(struct mScriptContext* context, struct mScriptCoreAdapter* adapter, bool clear) {
struct TableIterator iter;
if (mScriptTableIteratorStart(&adapter->memory, &iter)) {
while (true) {
struct mScriptValue* weakref = mScriptTableIteratorGetValue(&adapter->memory, &iter);
if (weakref) {
if (clear) {
mScriptContextClearWeakref(context, weakref->value.s32);
}
mScriptValueDeref(weakref);
}
if (!mScriptTableIteratorNext(&adapter->memory, &iter)) {
break;
}
}
}
mScriptTableClear(&adapter->memory);
}
static void _rebuildMemoryMap(struct mScriptContext* context, struct mScriptCoreAdapter* adapter) {
_clearMemoryMap(context, adapter, true);
const struct mCoreMemoryBlock* blocks;
size_t nBlocks = adapter->core->listMemoryBlocks(adapter->core, &blocks);
size_t i;
for (i = 0; i < nBlocks; ++i) {
if (blocks[i].flags == mCORE_MEMORY_VIRTUAL) {
continue;
}
struct mScriptMemoryDomain* memadapter = calloc(1, sizeof(*memadapter));
memadapter->core = adapter->core;
memcpy(&memadapter->block, &blocks[i], sizeof(memadapter->block));
struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptMemoryDomain));
value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
value->value.opaque = memadapter;
struct mScriptValue* key = mScriptStringCreateFromUTF8(blocks[i].internalName);
mScriptTableInsert(&adapter->memory, key, mScriptContextMakeWeakref(context, value));
mScriptValueDeref(key);
}
}
static void _mScriptCoreAdapterDeinit(struct mScriptCoreAdapter* adapter) {
_clearMemoryMap(adapter->context, adapter, false);
adapter->memory.type->free(&adapter->memory);
}
static struct mScriptValue* _mScriptCoreAdapterGet(struct mScriptCoreAdapter* adapter, const char* name) {
struct mScriptValue val;
struct mScriptValue core = mSCRIPT_MAKE(S(mCore), adapter->core);
if (!mScriptObjectGet(&core, name, &val)) {
return NULL;
}
struct mScriptValue* ret = malloc(sizeof(*ret));
memcpy(ret, &val, sizeof(*ret));
ret->refs = 1;
return ret;
}
static void _mScriptCoreAdapterReset(struct mScriptCoreAdapter* adapter) {
adapter->core->reset(adapter->core);
mScriptContextTriggerCallback(adapter->context, "reset");
}
mSCRIPT_DECLARE_STRUCT(mScriptCoreAdapter);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptCoreAdapter, W(mCore), _get, _mScriptCoreAdapterGet, 1, CHARP, name);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, _deinit, _mScriptCoreAdapterDeinit, 0);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCoreAdapter, reset, _mScriptCoreAdapterReset, 0);
mSCRIPT_DEFINE_STRUCT(mScriptCoreAdapter)
mSCRIPT_DEFINE_CLASS_DOCSTRING(
"A wrapper around a struct::mCore object that exposes more functionality. "
"It can be implicity cast to a Core object, and exposes the same methods. "
"Please see the documentation on struct::mCore for details on those methods."
)
mSCRIPT_DEFINE_STRUCT_MEMBER_NAMED(mScriptCoreAdapter, PS(mCore), _core, core)
mSCRIPT_DEFINE_DOCSTRING("A table containing a platform-specific set of struct::mScriptMemoryDomain objects")
mSCRIPT_DEFINE_STRUCT_MEMBER(mScriptCoreAdapter, TABLE, memory)
mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptCoreAdapter)
mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptCoreAdapter)
mSCRIPT_DEFINE_DOCSTRING("Reset the emulation. As opposed to struct::mCore.reset, this version calls the **reset** callback")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCoreAdapter, reset)
mSCRIPT_DEFINE_STRUCT_CAST_TO_MEMBER(mScriptCoreAdapter, S(mCore), _core)
mSCRIPT_DEFINE_STRUCT_CAST_TO_MEMBER(mScriptCoreAdapter, CS(mCore), _core)
mSCRIPT_DEFINE_END;
void mScriptContextAttachCore(struct mScriptContext* context, struct mCore* core) {
struct mScriptValue* coreValue = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCoreAdapter));
struct mScriptCoreAdapter* adapter = calloc(1, sizeof(*adapter));
adapter->core = core;
adapter->context = context;
adapter->memory.refs = mSCRIPT_VALUE_UNREF;
adapter->memory.flags = 0;
adapter->memory.type = mSCRIPT_TYPE_MS_TABLE;
adapter->memory.type->alloc(&adapter->memory);
_rebuildMemoryMap(context, adapter);
coreValue->value.opaque = adapter;
coreValue->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
mScriptContextSetGlobal(context, "emu", coreValue);
}
void mScriptContextDetachCore(struct mScriptContext* context) {
struct mScriptValue* value = HashTableLookup(&context->rootScope, "emu");
if (!value) {
return;
}
value = mScriptContextAccessWeakref(context, value);
if (!value) {
return;
}
_clearMemoryMap(context, value->value.opaque, true);
mScriptContextRemoveGlobal(context, "emu");
}
static struct mScriptTextBuffer* _mScriptConsoleCreateBuffer(struct mScriptConsole* lib, const char* name) {
struct mScriptTextBuffer* buffer = lib->textBufferFactory(lib->textBufferContext);
buffer->init(buffer, name);
return buffer;
}
static void mScriptConsoleLog(struct mScriptConsole* console, struct mScriptString* msg) {
if (console->logger) {
mLogExplicit(console->logger, _mLOG_CAT_SCRIPT, mLOG_INFO, "%s", msg->buffer);
} else {
mLog(_mLOG_CAT_SCRIPT, mLOG_INFO, "%s", msg->buffer);
}
}
static void mScriptConsoleWarn(struct mScriptConsole* console, struct mScriptString* msg) {
if (console->logger) {
mLogExplicit(console->logger, _mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg->buffer);
} else {
mLog(_mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg->buffer);
}
}
static void mScriptConsoleError(struct mScriptConsole* console, struct mScriptString* msg) {
if (console->logger) {
mLogExplicit(console->logger, _mLOG_CAT_SCRIPT, mLOG_ERROR, "%s", msg->buffer);
} else {
mLog(_mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg->buffer);
}
}
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptConsole, log, mScriptConsoleLog, 1, STR, msg);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptConsole, warn, mScriptConsoleWarn, 1, STR, msg);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptConsole, error, mScriptConsoleError, 1, STR, msg);
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptConsole, S(mScriptTextBuffer), createBuffer, _mScriptConsoleCreateBuffer, 1, CHARP, name);
mSCRIPT_DEFINE_STRUCT(mScriptConsole)
mSCRIPT_DEFINE_CLASS_DOCSTRING(
"A global singleton object `console` that can be used for presenting textual information to the user via a console."
)
mSCRIPT_DEFINE_DOCSTRING("Print a log to the console")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptConsole, log)
mSCRIPT_DEFINE_DOCSTRING("Print a warning to the console")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptConsole, warn)
mSCRIPT_DEFINE_DOCSTRING("Print an error to the console")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptConsole, error)
mSCRIPT_DEFINE_DOCSTRING("Create a text buffer that can be used to display custom information")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptConsole, createBuffer)
mSCRIPT_DEFINE_END;
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptConsole, createBuffer)
mSCRIPT_MAKE_CHARP(NULL)
mSCRIPT_DEFINE_DEFAULTS_END;
void mScriptContextAttachLogger(struct mScriptContext* context, struct mLogger* logger) {
struct mScriptValue* value = mScriptContextEnsureGlobal(context, "console", mSCRIPT_TYPE_MS_S(mScriptConsole));
struct mScriptConsole* console = value->value.opaque;
if (!console) {
console = calloc(1, sizeof(*console));
value->value.opaque = console;
value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
}
console->logger = logger;
}
void mScriptContextDetachLogger(struct mScriptContext* context) {
struct mScriptValue* value = mScriptContextGetGlobal(context, "console");
if (!value) {
return;
}
struct mScriptConsole* console = value->value.opaque;
console->logger = mLogGetContext();
}
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, deinit, 0);
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mScriptTextBuffer, U32, getX, 0);
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mScriptTextBuffer, U32, getY, 0);
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mScriptTextBuffer, U32, cols, 0);
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mScriptTextBuffer, U32, rows, 0);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, print, 1, CHARP, text);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, clear, 0);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, setSize, 2, U32, cols, U32, rows);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, moveCursor, 2, U32, x, U32, y);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, advance, 1, S32, adv);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(mScriptTextBuffer, setName, 1, CHARP, name);
mSCRIPT_DEFINE_STRUCT(mScriptTextBuffer)
mSCRIPT_DEFINE_CLASS_DOCSTRING(
"An object that can be used to present texual data to the user. It is displayed monospaced, "
"and text can be edited after sending by moving the cursor or clearing the buffer."
)
mSCRIPT_DEFINE_STRUCT_DEINIT_NAMED(mScriptTextBuffer, deinit)
mSCRIPT_DEFINE_DOCSTRING("Get the current x position of the cursor")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, getX)
mSCRIPT_DEFINE_DOCSTRING("Get the current y position of the cursor")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, getY)
mSCRIPT_DEFINE_DOCSTRING("Get number of columns in the buffer")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, cols)
mSCRIPT_DEFINE_DOCSTRING("Get number of rows in the buffer")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, rows)
mSCRIPT_DEFINE_DOCSTRING("Print a string to the buffer")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, print)
mSCRIPT_DEFINE_DOCSTRING("Clear the buffer")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, clear)
mSCRIPT_DEFINE_DOCSTRING("Set the number of rows and columns")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, setSize)
mSCRIPT_DEFINE_DOCSTRING("Set the posiiton of the cursor")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, moveCursor)
mSCRIPT_DEFINE_DOCSTRING("Advance the cursor a number of columns")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, advance)
mSCRIPT_DEFINE_DOCSTRING("Set the user-visible name of this buffer")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptTextBuffer, setName)
mSCRIPT_DEFINE_END;
void mScriptContextSetTextBufferFactory(struct mScriptContext* context, mScriptContextBufferFactory factory, void* cbContext) {
struct mScriptValue* value = mScriptContextEnsureGlobal(context, "console", mSCRIPT_TYPE_MS_S(mScriptConsole));
struct mScriptConsole* console = value->value.opaque;
if (!console) {
console = calloc(1, sizeof(*console));
console->logger = mLogGetContext();
value->value.opaque = console;
value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
}
console->textBufferFactory = factory;
console->textBufferContext = cbContext;
}

View File

@ -457,6 +457,9 @@ bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) {
else {
bool success = _savePNGState(core, vf, &extdata);
mStateExtdataDeinit(&extdata);
if (cheatVf) {
cheatVf->close(cheatVf);
}
return success;
}
#endif

335
src/core/test/scripting.c Normal file
View File

@ -0,0 +1,335 @@
/* Copyright (c) 2013-2022 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/core/core.h>
#include <mgba/core/log.h>
#include <mgba/core/scripting.h>
#include <mgba/internal/script/lua.h>
#include <mgba/script/context.h>
#include <mgba/script/types.h>
#ifdef M_CORE_GBA
#include <mgba/internal/gba/memory.h>
#define TEST_PLATFORM mPLATFORM_GBA
#define RAM_BASE BASE_WORKING_IRAM
#elif defined(M_CORE_GB)
#include <mgba/internal/gb/memory.h>
#define TEST_PLATFORM mPLATFORM_GB
#define RAM_BASE GB_BASE_WORKING_RAM_BANK0
#else
#error "Need a valid platform for testing"
#endif
struct mScriptTestLogger {
struct mLogger d;
char* log;
char* warn;
char* error;
};
static const uint8_t _fakeGBROM[0x4000] = {
[0x100] = 0x18, // Loop forever
[0x101] = 0xFE, // jr, $-2
[0x102] = 0xCE, // Enough of the header to fool the core
[0x103] = 0xED,
[0x104] = 0x66,
[0x105] = 0x66,
};
#define SETUP_LUA \
struct mScriptContext context; \
mScriptContextInit(&context); \
struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA)
#define CREATE_CORE \
struct mCore* core = mCoreCreate(TEST_PLATFORM); \
assert_non_null(core); \
assert_true(core->init(core)); \
switch (core->platform(core)) { \
case mPLATFORM_GBA: \
core->busWrite32(core, 0x020000C0, 0xEAFFFFFE); \
break; \
case mPLATFORM_GB: \
assert_true(core->loadROM(core, VFileFromConstMemory(_fakeGBROM, sizeof(_fakeGBROM)))); \
break; \
case mPLATFORM_NONE: \
break; \
} \
mCoreInitConfig(core, NULL); \
mScriptContextAttachCore(&context, core)
#define TEARDOWN_CORE \
mCoreConfigDeinit(&core->config); \
core->deinit(core)
#define LOAD_PROGRAM(PROG) \
do { \
struct VFile* vf = VFileFromConstMemory(PROG, strlen(PROG)); \
assert_true(lua->load(lua, NULL, vf)); \
vf->close(vf); \
} while(0)
#define TEST_VALUE(TYPE, NAME, VALUE) \
do { \
struct mScriptValue val = mSCRIPT_MAKE(TYPE, VALUE); \
struct mScriptValue* global = lua->getGlobal(lua, NAME); \
assert_non_null(global); \
assert_true(global->type->equal(global, &val)); \
mScriptValueDeref(global); \
} while(0)
static void _mScriptTestLog(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) {
UNUSED(category);
struct mScriptTestLogger* logger = (struct mScriptTestLogger*) log;
char* message;
#ifdef HAVE_VASPRINTF
vasprintf(&message, format, args);
#else
char messageBuf[64];
vsnprintf(messageBuf, format, args);
message = strdup(messageBuf);
#endif
switch (level) {
case mLOG_INFO:
if (logger->log) {
free(logger->log);
}
logger->log = message;
break;
case mLOG_WARN:
if (logger->warn) {
free(logger->warn);
}
logger->warn = message;
break;
case mLOG_ERROR:
if (logger->error) {
free(logger->error);
}
logger->error = message;
break;
default:
free(message);
}
}
static void mScriptTestLoggerInit(struct mScriptTestLogger* logger) {
memset(logger, 0, sizeof(*logger));
logger->d.log = _mScriptTestLog;
}
static void mScriptTestLoggerDeinit(struct mScriptTestLogger* logger) {
if (logger->log) {
free(logger->log);
}
if (logger->warn) {
free(logger->warn);
}
if (logger->error) {
free(logger->error);
}
}
M_TEST_SUITE_SETUP(mScriptCore) {
if (mSCRIPT_ENGINE_LUA->init) {
mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA);
}
return 0;
}
M_TEST_SUITE_TEARDOWN(mScriptCore) {
if (mSCRIPT_ENGINE_LUA->deinit) {
mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA);
}
return 0;
}
M_TEST_DEFINE(globals) {
SETUP_LUA;
CREATE_CORE;
core->reset(core);
LOAD_PROGRAM("assert(emu)");
assert_true(lua->run(lua));
TEARDOWN_CORE;
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(infoFuncs) {
SETUP_LUA;
CREATE_CORE;
core->reset(core);
LOAD_PROGRAM(
"frequency = emu:frequency()\n"
"frameCycles = emu:frameCycles()\n"
);
assert_true(lua->run(lua));
TEST_VALUE(S32, "frequency", core->frequency(core));
TEST_VALUE(S32, "frameCycles", core->frameCycles(core));
TEARDOWN_CORE;
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(detach) {
SETUP_LUA;
CREATE_CORE;
core->reset(core);
LOAD_PROGRAM(
"assert(emu)\n"
"assert(emu.memory)\n"
"a = emu\n"
"b = emu.memory\n"
);
assert_true(lua->run(lua));
mScriptContextDetachCore(&context);
LOAD_PROGRAM(
"assert(not emu)\n"
);
assert_true(lua->run(lua));
LOAD_PROGRAM(
"a:frequency()\n"
);
assert_false(lua->run(lua));
LOAD_PROGRAM(
"assert(memory.cart0)\n"
);
assert_false(lua->run(lua));
TEARDOWN_CORE;
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(runFrame) {
SETUP_LUA;
CREATE_CORE;
core->reset(core);
LOAD_PROGRAM(
"frame = emu:currentFrame()\n"
"emu:runFrame()\n"
);
int i;
for (i = 0; i < 5; ++i) {
assert_true(lua->run(lua));
TEST_VALUE(S32, "frame", i);
}
TEARDOWN_CORE;
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(memoryRead) {
SETUP_LUA;
CREATE_CORE;
core->reset(core);
LOAD_PROGRAM(
"a8 = emu:read8(base + 0)\n"
"b8 = emu:read8(base + 1)\n"
"c8 = emu:read8(base + 2)\n"
"d8 = emu:read8(base + 3)\n"
"a16 = emu:read16(base + 4)\n"
"b16 = emu:read16(base + 6)\n"
"a32 = emu:read32(base + 8)\n"
);
int i;
for (i = 0; i < 12; ++i) {
core->busWrite8(core, RAM_BASE + i, i + 1);
}
struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE);
lua->setGlobal(lua, "base", &base);
assert_true(lua->run(lua));
TEST_VALUE(S32, "a8", 1);
TEST_VALUE(S32, "b8", 2);
TEST_VALUE(S32, "c8", 3);
TEST_VALUE(S32, "d8", 4);
TEST_VALUE(S32, "a16", 0x0605);
TEST_VALUE(S32, "b16", 0x0807);
TEST_VALUE(S32, "a32", 0x0C0B0A09);
TEARDOWN_CORE;
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(memoryWrite) {
SETUP_LUA;
CREATE_CORE;
core->reset(core);
LOAD_PROGRAM(
"emu:write8(base + 0, 1)\n"
"emu:write8(base + 1, 2)\n"
"emu:write8(base + 2, 3)\n"
"emu:write8(base + 3, 4)\n"
"emu:write16(base + 4, 0x0605)\n"
"emu:write16(base + 6, 0x0807)\n"
"emu:write32(base + 8, 0x0C0B0A09)\n"
);
struct mScriptValue base = mSCRIPT_MAKE_S32(RAM_BASE);
lua->setGlobal(lua, "base", &base);
assert_true(lua->run(lua));
int i;
for (i = 0; i < 12; ++i) {
assert_int_equal(core->busRead8(core, RAM_BASE + i), i + 1);
}
TEARDOWN_CORE;
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(logging) {
SETUP_LUA;
struct mScriptTestLogger logger;
mScriptTestLoggerInit(&logger);
mScriptContextAttachLogger(&context, &logger.d);
LOAD_PROGRAM(
"assert(console)\n"
"console:log(\"log\")\n"
"console:warn(\"warn\")\n"
"console:error(\"error\")\n"
"a = console\n"
);
assert_true(lua->run(lua));
assert_non_null(logger.log);
assert_non_null(logger.warn);
assert_non_null(logger.error);
assert_string_equal(logger.log, "log");
assert_string_equal(logger.warn, "warn");
assert_string_equal(logger.error, "error");
mScriptContextDetachLogger(&context);
mScriptTestLoggerDeinit(&logger);
mScriptContextDeinit(&context);
}
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore,
cmocka_unit_test(globals),
cmocka_unit_test(infoFuncs),
cmocka_unit_test(detach),
cmocka_unit_test(runFrame),
cmocka_unit_test(memoryRead),
cmocka_unit_test(memoryWrite),
cmocka_unit_test(logging),
)

View File

@ -7,6 +7,10 @@
#include <mgba/core/blip_buf.h>
#include <mgba/core/core.h>
#ifdef ENABLE_SCRIPTING
#include <mgba/script/context.h>
#include <mgba/core/scripting.h>
#endif
#include <mgba/core/serialize.h>
#include <mgba-util/patch.h>
#include <mgba-util/vfs.h>
@ -183,6 +187,42 @@ void _coreShutdown(void* context) {
_changeState(thread->impl, mTHREAD_EXITING, true);
}
#ifdef ENABLE_SCRIPTING
#define ADD_CALLBACK(NAME) \
void _script_ ## NAME(void* context) { \
struct mCoreThread* threadContext = context; \
if (!threadContext->scriptContext) { \
return; \
} \
mScriptContextTriggerCallback(threadContext->scriptContext, #NAME); \
}
ADD_CALLBACK(frame)
ADD_CALLBACK(crashed)
ADD_CALLBACK(sleep)
ADD_CALLBACK(stop)
ADD_CALLBACK(keysRead)
ADD_CALLBACK(savedataUpdated)
ADD_CALLBACK(alarm)
#undef ADD_CALLBACK
#define CALLBACK(NAME) _script_ ## NAME
static void _mCoreThreadAddCallbacks(struct mCoreThread* threadContext) {
struct mCoreCallbacks callbacks = {
.videoFrameEnded = CALLBACK(frame),
.coreCrashed = CALLBACK(crashed),
.sleep = CALLBACK(sleep),
.shutdown = CALLBACK(stop),
.keysRead = CALLBACK(keysRead),
.savedataUpdated = CALLBACK(savedataUpdated),
.alarm = CALLBACK(alarm),
.context = threadContext
};
threadContext->core->addCoreCallbacks(threadContext->core, &callbacks);
}
#endif
static THREAD_ENTRY _mCoreThreadRun(void* context) {
struct mCoreThread* threadContext = context;
#ifdef USE_PTHREADS
@ -219,10 +259,30 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
mLogFilterLoad(threadContext->logger.d.filter, &core->config);
}
#ifdef ENABLE_SCRIPTING
struct mScriptContext* scriptContext = threadContext->scriptContext;
if (scriptContext) {
mScriptContextAttachCore(scriptContext, core);
_mCoreThreadAddCallbacks(threadContext);
}
#endif
mCoreThreadRewindParamsChanged(threadContext);
if (threadContext->startCallback) {
threadContext->startCallback(threadContext);
}
#ifdef ENABLE_SCRIPTING
// startCallback could add a script context
if (scriptContext != threadContext->scriptContext) {
scriptContext = threadContext->scriptContext;
if (scriptContext) {
_mCoreThreadAddCallbacks(threadContext);
}
}
if (scriptContext) {
mScriptContextTriggerCallback(scriptContext, "start");
}
#endif
core->reset(core);
threadContext->impl->core = core;
@ -232,6 +292,19 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
threadContext->resetCallback(threadContext);
}
#ifdef ENABLE_SCRIPTING
// resetCallback could add a script context
if (scriptContext != threadContext->scriptContext) {
scriptContext = threadContext->scriptContext;
if (scriptContext) {
_mCoreThreadAddCallbacks(threadContext);
}
}
if (scriptContext) {
mScriptContextTriggerCallback(scriptContext, "reset");
}
#endif
struct mCoreThreadInternal* impl = threadContext->impl;
bool wasPaused = false;
int pendingRequests = 0;
@ -277,6 +350,14 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
MutexLock(&impl->stateMutex);
}
}
#ifdef ENABLE_SCRIPTING
if (scriptContext != threadContext->scriptContext) {
scriptContext = threadContext->scriptContext;
if (scriptContext) {
_mCoreThreadAddCallbacks(threadContext);
}
}
#endif
if (wasPaused && !(impl->requested & mTHREAD_REQ_PAUSE)) {
break;
}
@ -318,6 +399,11 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
if (threadContext->resetCallback) {
threadContext->resetCallback(threadContext);
}
#ifdef ENABLE_SCRIPTING
if (scriptContext) {
mScriptContextTriggerCallback(scriptContext, "reset");
}
#endif
}
if (pendingRequests & mTHREAD_REQ_RUN_ON) {
if (threadContext->run) {
@ -337,6 +423,12 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
if (threadContext->cleanCallback) {
threadContext->cleanCallback(threadContext);
}
#ifdef ENABLE_SCRIPTING
if (scriptContext) {
mScriptContextTriggerCallback(scriptContext, "shutdown");
mScriptContextDetachCore(scriptContext);
}
#endif
core->clearCoreCallbacks(core);
if (threadContext->logger.d.filter == &filter) {

View File

@ -40,14 +40,14 @@ static void _SoftReset(struct GBA* gba) {
ARMSetPrivilegeMode(cpu, MODE_IRQ);
cpu->spsr.packed = 0;
cpu->gprs[ARM_LR] = 0;
cpu->gprs[ARM_SP] = SP_BASE_IRQ;
cpu->gprs[ARM_SP] = GBA_SP_BASE_IRQ;
ARMSetPrivilegeMode(cpu, MODE_SUPERVISOR);
cpu->spsr.packed = 0;
cpu->gprs[ARM_LR] = 0;
cpu->gprs[ARM_SP] = SP_BASE_SUPERVISOR;
cpu->gprs[ARM_SP] = GBA_SP_BASE_SUPERVISOR;
ARMSetPrivilegeMode(cpu, MODE_SYSTEM);
cpu->gprs[ARM_LR] = 0;
cpu->gprs[ARM_SP] = SP_BASE_SYSTEM;
cpu->gprs[ARM_SP] = GBA_SP_BASE_SYSTEM;
int8_t flag = ((int8_t*) gba->memory.iwram)[0x7FFA];
memset(((int8_t*) gba->memory.iwram) + SIZE_WORKING_IRAM - 0x200, 0, 0x200);
if (flag) {

View File

@ -718,7 +718,7 @@ void _eReaderReadData(struct GBACartEReader* ereader) {
if (led > 0x4000) {
led = 0x4000;
}
GBARaiseIRQ(ereader->p, IRQ_GAMEPAK, -led);
GBARaiseIRQ(ereader->p, GBA_IRQ_GAMEPAK, -led);
}
}

View File

@ -208,7 +208,7 @@ void _dmaEvent(struct mTiming* timing, void* context, uint32_t cyclesLate) {
dma->nextDest = dma->dest;
}
if (GBADMARegisterIsDoIRQ(dma->reg)) {
GBARaiseIRQ(gba, IRQ_DMA0 + memory->activeDMA, cyclesLate);
GBARaiseIRQ(gba, GBA_IRQ_DMA0 + memory->activeDMA, cyclesLate);
}
GBADMAUpdate(gba);
}

View File

@ -102,7 +102,7 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
gate->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0;
gate->d.p->siocnt = GBASIONormalClearStart(gate->d.p->siocnt);
if (GBASIONormalIsIrq(gate->d.p->siocnt)) {
GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate);
GBARaiseIRQ(gate->d.p->p, GBA_IRQ_SIO, cyclesLate);
}
return;
}
@ -194,6 +194,6 @@ void _battlechipTransferEvent(struct mTiming* timing, void* user, uint32_t cycle
gate->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = reply;
if (GBASIOMultiplayerIsIrq(gate->d.p->siocnt)) {
GBARaiseIRQ(gate->d.p->p, IRQ_SIO, cyclesLate);
GBARaiseIRQ(gate->d.p->p, GBA_IRQ_SIO, cyclesLate);
}
}

View File

@ -198,11 +198,11 @@ void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh) {
void GBAReset(struct ARMCore* cpu) {
ARMSetPrivilegeMode(cpu, MODE_IRQ);
cpu->gprs[ARM_SP] = SP_BASE_IRQ;
cpu->gprs[ARM_SP] = GBA_SP_BASE_IRQ;
ARMSetPrivilegeMode(cpu, MODE_SUPERVISOR);
cpu->gprs[ARM_SP] = SP_BASE_SUPERVISOR;
cpu->gprs[ARM_SP] = GBA_SP_BASE_SUPERVISOR;
ARMSetPrivilegeMode(cpu, MODE_SYSTEM);
cpu->gprs[ARM_SP] = SP_BASE_SYSTEM;
cpu->gprs[ARM_SP] = GBA_SP_BASE_SYSTEM;
struct GBA* gba = (struct GBA*) cpu->master;
gba->memory.savedata.maskWriteback = false;
@ -465,7 +465,7 @@ void GBAYankROM(struct GBA* gba) {
gba->yankedRomSize = gba->memory.romSize;
gba->memory.romSize = 0;
gba->memory.romMask = 0;
GBARaiseIRQ(gba, IRQ_GAMEPAK, 0);
GBARaiseIRQ(gba, GBA_IRQ_GAMEPAK, 0);
}
void GBALoadBIOS(struct GBA* gba, struct VFile* vf) {
@ -553,7 +553,7 @@ void GBAHalt(struct GBA* gba) {
}
void GBAStop(struct GBA* gba) {
int validIrqs = (1 << IRQ_GAMEPAK) | (1 << IRQ_KEYPAD) | (1 << IRQ_SIO);
int validIrqs = (1 << GBA_IRQ_GAMEPAK) | (1 << GBA_IRQ_KEYPAD) | (1 << GBA_IRQ_SIO);
int sleep = gba->memory.io[REG_IE >> 1] & validIrqs;
size_t c;
for (c = 0; c < mCoreCallbacksListSize(&gba->coreCallbacks); ++c) {
@ -920,9 +920,9 @@ void GBATestKeypadIRQ(struct GBA* gba) {
if (keysLast == keysActive) {
return;
}
GBARaiseIRQ(gba, IRQ_KEYPAD, 0);
GBARaiseIRQ(gba, GBA_IRQ_KEYPAD, 0);
} else if (!isAnd && (keysActive & keycnt)) {
GBARaiseIRQ(gba, IRQ_KEYPAD, 0);
GBARaiseIRQ(gba, GBA_IRQ_KEYPAD, 0);
} else {
gba->keysLast = 0x400;
}

View File

@ -188,7 +188,7 @@ void GBASIOWriteSIOCNT(struct GBASIO* sio, uint16_t value) {
if ((value & 0x0081) == 0x0081) {
if (value & 0x4000) {
// TODO: Test this on hardware to see if this is correct
GBARaiseIRQ(sio->p, IRQ_SIO, 0);
GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0);
}
value &= ~0x0080;
}

View File

@ -141,7 +141,7 @@ void _gbpSioProcessEvents(struct mTiming* timing, void* user, uint32_t cyclesLat
gbp->p->memory.io[REG_SIODATA32_LO >> 1] = tx;
gbp->p->memory.io[REG_SIODATA32_HI >> 1] = tx >> 16;
if (GBASIONormalIsIrq(gbp->d.p->siocnt)) {
GBARaiseIRQ(gbp->p, IRQ_SIO, cyclesLate);
GBARaiseIRQ(gbp->p, GBA_IRQ_SIO, cyclesLate);
}
gbp->d.p->siocnt = GBASIONormalClearStart(gbp->d.p->siocnt);
gbp->p->memory.io[REG_SIOCNT >> 1] = gbp->d.p->siocnt & ~0x0080;

View File

@ -46,7 +46,7 @@ int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command
case JOY_RESET:
sio->p->p->memory.io[REG_JOYCNT >> 1] |= JOYCNT_RESET;
if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) {
GBARaiseIRQ(sio->p->p, IRQ_SIO, 0);
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
// Fall through
case JOY_POLL:
@ -68,7 +68,7 @@ int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command
mLOG(GBA_SIO, DEBUG, "JOY recv: %02X (%02X)", data[0], sio->p->p->memory.io[REG_JOYCNT >> 1]);
if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) {
GBARaiseIRQ(sio->p->p, IRQ_SIO, 0);
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
return 1;
case JOY_TRANS:
@ -84,7 +84,7 @@ int GBASIOJOYSendCommand(struct GBASIODriver* sio, enum GBASIOJOYCommand command
mLOG(GBA_SIO, DEBUG, "JOY trans: %02X%02X%02X%02X:%02X (%02X)", data[0], data[1], data[2], data[3], data[4], sio->p->p->memory.io[REG_JOYCNT >> 1]);
if (sio->p->p->memory.io[REG_JOYCNT >> 1] & 0x40) {
GBARaiseIRQ(sio->p->p, IRQ_SIO, 0);
GBARaiseIRQ(sio->p->p, GBA_IRQ_SIO, 0);
}
return 5;
}

View File

@ -230,7 +230,7 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) {
sio->siocnt = GBASIOMultiplayerClearBusy(sio->siocnt);
sio->siocnt = GBASIOMultiplayerSetId(sio->siocnt, node->id);
if (GBASIOMultiplayerIsIrq(sio->siocnt)) {
GBARaiseIRQ(sio->p, IRQ_SIO, 0);
GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0);
}
break;
case SIO_NORMAL_8:
@ -243,7 +243,7 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) {
node->d.p->p->memory.io[REG_SIODATA8 >> 1] = 0xFFFF;
}
if (GBASIONormalIsIrq(sio->siocnt)) {
GBARaiseIRQ(sio->p, IRQ_SIO, 0);
GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0);
}
break;
case SIO_NORMAL_32:
@ -258,7 +258,7 @@ static void _finishTransfer(struct GBASIOLockstepNode* node) {
node->d.p->p->memory.io[REG_SIODATA32_HI >> 1] = 0xFFFF;
}
if (GBASIONormalIsIrq(sio->siocnt)) {
GBARaiseIRQ(sio->p, IRQ_SIO, 0);
GBARaiseIRQ(sio->p, GBA_IRQ_SIO, 0);
}
break;
default:

View File

@ -11,7 +11,7 @@
static void GBATimerIrq(struct GBA* gba, int timerId, uint32_t cyclesLate) {
struct GBATimer* timer = &gba->timers[timerId];
if (GBATimerFlagsIsDoIrq(timer->flags)) {
GBARaiseIRQ(gba, IRQ_TIMER0 + timerId, cyclesLate);
GBARaiseIRQ(gba, GBA_IRQ_TIMER0 + timerId, cyclesLate);
}
}

View File

@ -180,7 +180,7 @@ void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) {
if (video->vcount == GBARegisterDISPSTATGetVcountSetting(dispstat)) {
dispstat = GBARegisterDISPSTATFillVcounter(dispstat);
if (GBARegisterDISPSTATIsVcounterIRQ(dispstat)) {
GBARaiseIRQ(video->p, IRQ_VCOUNTER, cyclesLate);
GBARaiseIRQ(video->p, GBA_IRQ_VCOUNTER, cyclesLate);
}
} else {
dispstat = GBARegisterDISPSTATClearVcounter(dispstat);
@ -199,7 +199,7 @@ void _startHdraw(struct mTiming* timing, void* context, uint32_t cyclesLate) {
}
GBADMARunVblank(video->p, -cyclesLate);
if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {
GBARaiseIRQ(video->p, IRQ_VBLANK, cyclesLate);
GBARaiseIRQ(video->p, GBA_IRQ_VBLANK, cyclesLate);
}
GBAFrameEnded(video->p);
mCoreSyncPostFrame(video->p->sync);
@ -235,7 +235,7 @@ void _startHblank(struct mTiming* timing, void* context, uint32_t cyclesLate) {
GBADMARunDisplayStart(video->p, -cyclesLate);
}
if (GBARegisterDISPSTATIsHblankIRQ(dispstat)) {
GBARaiseIRQ(video->p, IRQ_HBLANK, cyclesLate - 6); // TODO: Where does this fudge factor come from?
GBARaiseIRQ(video->p, GBA_IRQ_HBLANK, cyclesLate - 6); // TODO: Where does this fudge factor come from?
}
video->shouldStall = 0;
video->p->memory.io[REG_DISPSTAT >> 1] = dispstat;

View File

@ -43,6 +43,11 @@ function(find_feature FEATURE_NAME FEATURE_REQUIRES)
elseif(DEFINED ${UREQUIRE}_INCLUDE_DIRS)
set(${UREQUIRE}_INCLUDE_DIRS ${${UREQUIRE}_INCLUDE_DIRS} PARENT_SCOPE)
endif()
if(DEFINED ${REQUIRE}_INCLUDE_DIR)
set(${UREQUIRE}_INCLUDE_DIR ${${REQUIRE}_INCLUDE_DIR} PARENT_SCOPE)
elseif(DEFINED ${UREQUIRE}_INCLUDE_DIR)
set(${UREQUIRE}_INCLUDE_DIR ${${UREQUIRE}_INCLUDE_DIR} PARENT_SCOPE)
endif()
if(DEFINED ${REQUIRE}_VERSION_STRING)
set(${UREQUIRE}_VERSION_STRING ${${REQUIRE}_VERSION_STRING} PARENT_SCOPE)
elseif(DEFINED ${UREQUIRE}_VERSION_STRING)

View File

@ -88,6 +88,7 @@ set(SOURCE_FILES
AudioProcessor.cpp
CheatsModel.cpp
CheatsView.cpp
CheckBoxDelegate.cpp
ConfigController.cpp
ColorPicker.cpp
CoreManager.cpp
@ -250,11 +251,22 @@ if(USE_DISCORD_RPC)
list(APPEND SOURCE_FILES DiscordCoordinator.cpp)
endif()
if(ENABLE_SCRIPTING)
list(APPEND SOURCE_FILES
ScriptingController.cpp
ScriptingTextBuffer.cpp
ScriptingView.cpp)
list(APPEND UI_FILES
ScriptingView.ui)
endif()
if(TARGET Qt6::Core)
qt_add_resources(RESOURCES resources.qrc)
else()
qt5_add_resources(RESOURCES resources.qrc)
endif()
if(BUILD_UPDATER)
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc INPUT ${CMAKE_CURRENT_SOURCE_DIR}/updater.qrc.in)
if(TARGET Qt6::Core)
@ -294,6 +306,10 @@ endif()
if(BUILD_GL OR BUILD_GLES2 OR BUILD_EPOXY)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/shaders DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt)
endif()
if(ENABLE_SCRIPTING AND USE_LUA)
message(STATUS ${CMAKE_SOURCE_DIR}/res/scripts)
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/scripts DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt)
endif()
install(FILES ${CMAKE_SOURCE_DIR}/res/nointro.dat DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt)
if(NOT WIN32 AND NOT APPLE)
if(DATADIR MATCHES "^\\.[.\\]")

View File

@ -7,11 +7,13 @@
#include "GBAApp.h"
#include "CoreController.h"
#include "LogController.h"
#include <QBoxLayout>
#include <QButtonGroup>
#include <QClipboard>
#include <QRadioButton>
#include <QRegularExpression>
#include <mgba/core/cheats.h>
#ifdef M_CORE_GBA
@ -110,7 +112,7 @@ void CheatsView::removeSet() {
return;
}
CoreController::Interrupter interrupter(m_controller);
for (const QModelIndex& index : selection) {
for (const QModelIndex& index ATTRIBUTE_UNUSED : selection) {
m_model.removeAt(selection[0]);
}
}
@ -149,18 +151,40 @@ void CheatsView::enterCheat() {
index = m_model.index(m_model.rowCount() - 1, 0, QModelIndex());
m_ui.cheatList->selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
}
// TODO: Update API to handle this splitting in the core
QRegularExpression regexp("\\s");
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
QStringList cheats = m_ui.codeEntry->toPlainText().split('\n', Qt::SkipEmptyParts);
QStringList cheats = m_ui.codeEntry->toPlainText().split(regexp, Qt::SkipEmptyParts);
#else
QStringList cheats = m_ui.codeEntry->toPlainText().split('\n', QString::SkipEmptyParts);
QStringList cheats = m_ui.codeEntry->toPlainText().split(regexp, QString::SkipEmptyParts);
#endif
int failure = 0;
QString buffer;
for (const QString& string : cheats) {
m_model.beginAppendRow(index);
mCheatAddLine(set, string.toUtf8().constData(), m_codeType);
if (!buffer.isEmpty()) {
buffer += " " + string;
if (mCheatAddLine(set, buffer.toUtf8().constData(), m_codeType)) {
buffer.clear();
} else if (mCheatAddLine(set, string.toUtf8().constData(), m_codeType)) {
buffer.clear();
} else {
buffer = string;
++failure;
}
} else if (!mCheatAddLine(set, string.toUtf8().constData(), m_codeType)) {
buffer = string;
}
m_model.endAppendRow();
}
if (!buffer.isEmpty()) {
++failure;
}
if (set->refresh) {
set->refresh(set, m_controller->cheatDevice());
}
if (failure) {
LOG(QT, ERROR) << tr("Some cheats could not be added. Please ensure they're formatted correctly and/or try other cheat types.");
}
m_ui.codeEntry->clear();
}

View File

@ -0,0 +1,49 @@
/* Copyright (c) 2013-2022 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 "CheckBoxDelegate.h"
#include <QStyle>
#include <QAbstractItemView>
#include <QtDebug>
using namespace QGBA;
CheckBoxDelegate::CheckBoxDelegate(QObject* parent)
: QStyledItemDelegate(parent)
{
// initializers only
}
void CheckBoxDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const {
QAbstractItemView* view = qobject_cast<QAbstractItemView*>(option.styleObject);
if (view && (index.flags() & Qt::ItemIsUserCheckable)) {
// Set up style options
QStyleOptionViewItem newOption(option);
initStyleOption(&newOption, index);
if (view->currentIndex() == index && (newOption.state & QStyle::State_HasFocus)) {
newOption.state |= QStyle::State_KeyboardFocusChange;
}
if (newOption.checkState == Qt::PartiallyChecked) {
newOption.state |= QStyle::State_NoChange;
} else if (newOption.checkState == Qt::Checked) {
newOption.state |= QStyle::State_On;
} else {
newOption.state |= QStyle::State_Off;
}
// Draw background
view->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &newOption, painter, view);
// Draw checkbox
int checkWidth = view->style()->pixelMetric(QStyle::PM_IndicatorWidth);
int checkHeight = view->style()->pixelMetric(QStyle::PM_IndicatorHeight);
int xMargin = (newOption.rect.width() - checkWidth) / 2;
int yMargin = (newOption.rect.height() - checkHeight) / 2;
newOption.rect.setRect(newOption.rect.left() + xMargin, newOption.rect.top() + yMargin, checkWidth, checkHeight);
view->style()->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &newOption, painter, view);
} else {
QStyledItemDelegate::paint(painter, option, index);
}
}

View File

@ -0,0 +1,19 @@
/* Copyright (c) 2013-2022 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/. */
#pragma once
#include <QStyledItemDelegate>
namespace QGBA {
class CheckBoxDelegate : public QStyledItemDelegate {
public:
CheckBoxDelegate(QObject* parent = nullptr);
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
};
}

View File

@ -301,9 +301,9 @@ void ConfigController::setQtOption(const QString& key, const QVariant& value, co
}
}
QList<QString> ConfigController::getMRU() const {
QList<QString> mru;
m_settings->beginGroup("mru");
QStringList ConfigController::getMRU(ConfigController::MRU mruType) const {
QStringList mru;
m_settings->beginGroup(mruName(mruType));
for (int i = 0; i < MRU_LIST_SIZE; ++i) {
QString item = m_settings->value(QString::number(i)).toString();
if (item.isNull()) {
@ -315,9 +315,9 @@ QList<QString> ConfigController::getMRU() const {
return mru;
}
void ConfigController::setMRU(const QList<QString>& mru) {
void ConfigController::setMRU(const QStringList& mru, ConfigController::MRU mruType) {
int i = 0;
m_settings->beginGroup("mru");
m_settings->beginGroup(mruName(mruType));
for (const QString& item : mru) {
m_settings->setValue(QString::number(i), item);
++i;
@ -331,6 +331,15 @@ void ConfigController::setMRU(const QList<QString>& mru) {
m_settings->endGroup();
}
constexpr const char* ConfigController::mruName(ConfigController::MRU mru) {
switch (mru) {
case MRU::ROM:
return "mru";
case MRU::Script:
return "recentScripts";
}
}
void ConfigController::write() {
mCoreConfigSave(&m_config);
m_settings->sync();

View File

@ -67,6 +67,11 @@ public:
constexpr static const char* const PORT = "qt";
static const int MRU_LIST_SIZE = 10;
enum class MRU {
ROM,
Script
};
ConfigController(QObject* parent = nullptr);
~ConfigController();
@ -84,8 +89,8 @@ public:
QVariant getArgvOption(const QString& key) const;
QVariant takeArgvOption(const QString& key);
QList<QString> getMRU() const;
void setMRU(const QList<QString>& mru);
QStringList getMRU(MRU = MRU::ROM) const;
void setMRU(const QStringList& mru, MRU = MRU::ROM);
Configuration* overrides() { return mCoreConfigGetOverrides(&m_config); }
void saveOverride(const Override&);
@ -114,7 +119,7 @@ public slots:
void write();
private:
void addArgvOption(const QString& key, const QVariant& value);
static constexpr const char* mruName(ConfigController::MRU);
Configuration* defaults() { return &m_config.defaultsTable; }

View File

@ -164,6 +164,9 @@ public slots:
void replaceGame(const QString&);
void yankPak();
void addKey(int key);
void clearKey(int key);
#ifdef USE_PNG
void screenshot();
#endif

View File

@ -11,14 +11,58 @@
using namespace QGBA;
QTextCharFormat LogWidget::s_warn;
QTextCharFormat LogWidget::s_error;
QTextCharFormat LogWidget::s_prompt;
LogWidget::LogWidget(QWidget* parent)
: QTextEdit(parent)
: QPlainTextEdit(parent)
{
setFont(GBAApp::app()->monospaceFont());
QPalette palette = QApplication::palette();
s_warn.setFontWeight(QFont::DemiBold);
s_warn.setFontItalic(true);
s_warn.setForeground(Qt::yellow);
s_warn.setBackground(QColor(255, 255, 0, 64));
s_error.setFontWeight(QFont::Bold);
s_error.setForeground(Qt::red);
s_error.setBackground(QColor(255, 0, 0, 64));
s_prompt.setForeground(palette.brush(QPalette::Disabled, QPalette::Text));
}
void LogWidget::log(const QString& line) {
moveCursor(QTextCursor::End);
insertPlainText(line);
textCursor().insertText(line, {});
if (m_newlineTerminated) {
textCursor().insertText("\n");
}
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}
void LogWidget::warn(const QString& line) {
moveCursor(QTextCursor::End);
textCursor().insertText(WARN_PREFIX + line, s_warn);
if (m_newlineTerminated) {
textCursor().insertText("\n");
}
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}
void LogWidget::error(const QString& line) {
moveCursor(QTextCursor::End);
textCursor().insertText(ERROR_PREFIX + line, s_error);
if (m_newlineTerminated) {
textCursor().insertText("\n");
}
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}
void LogWidget::echo(const QString& line) {
moveCursor(QTextCursor::End);
textCursor().insertText(PROMPT_PREFIX + line, s_prompt);
if (m_newlineTerminated) {
textCursor().insertText("\n");
}
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}

View File

@ -5,16 +5,36 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
#include <QTextEdit>
#include <QPlainTextEdit>
namespace QGBA {
class LogWidget : public QTextEdit {
class LogHighlighter;
class LogWidget : public QPlainTextEdit {
Q_OBJECT
public:
static constexpr const char* WARN_PREFIX = "[WARNING] ";
static constexpr const char* ERROR_PREFIX = "[ERROR] ";
static constexpr const char* PROMPT_PREFIX = "> ";
LogWidget(QWidget* parent = nullptr);
void setNewlineTerminated(bool newlineTerminated) { m_newlineTerminated = newlineTerminated; }
public slots:
void log(const QString&);
void warn(const QString&);
void error(const QString&);
void echo(const QString&);
private:
static QTextCharFormat s_warn;
static QTextCharFormat s_error;
static QTextCharFormat s_prompt;
bool m_newlineTerminated = false;
};
}

View File

@ -0,0 +1,133 @@
/* Copyright (c) 2013-2022 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 "ScriptingController.h"
#include "CoreController.h"
#include "ScriptingTextBuffer.h"
using namespace QGBA;
ScriptingController::ScriptingController(QObject* parent)
: QObject(parent)
{
m_logger.p = this;
m_logger.log = [](mLogger* log, int, enum mLogLevel level, const char* format, va_list args) {
Logger* logger = static_cast<Logger*>(log);
va_list argc;
va_copy(argc, args);
QString message = QString::vasprintf(format, argc);
va_end(argc);
switch (level) {
case mLOG_WARN:
emit logger->p->warn(message);
break;
case mLOG_ERROR:
emit logger->p->error(message);
break;
default:
emit logger->p->log(message);
break;
}
};
init();
}
ScriptingController::~ScriptingController() {
clearController();
mScriptContextDeinit(&m_scriptContext);
}
void ScriptingController::setController(std::shared_ptr<CoreController> controller) {
if (controller == m_controller) {
return;
}
clearController();
m_controller = controller;
CoreController::Interrupter interrupter(m_controller);
m_controller->thread()->scriptContext = &m_scriptContext;
if (m_controller->hasStarted()) {
mScriptContextAttachCore(&m_scriptContext, m_controller->thread()->core);
}
connect(m_controller.get(), &CoreController::stopping, this, &ScriptingController::clearController);
}
bool ScriptingController::loadFile(const QString& path) {
VFileDevice vf(path, QIODevice::ReadOnly);
return load(vf, path);
}
bool ScriptingController::load(VFileDevice& vf, const QString& name) {
if (!m_activeEngine) {
return false;
}
QByteArray utf8 = name.toUtf8();
CoreController::Interrupter interrupter(m_controller);
if (!m_activeEngine->load(m_activeEngine, utf8.constData(), vf) || !m_activeEngine->run(m_activeEngine)) {
emit error(QString::fromUtf8(m_activeEngine->getError(m_activeEngine)));
return false;
}
return true;
}
void ScriptingController::clearController() {
if (!m_controller) {
return;
}
{
CoreController::Interrupter interrupter(m_controller);
mScriptContextDetachCore(&m_scriptContext);
m_controller->thread()->scriptContext = nullptr;
}
m_controller.reset();
}
void ScriptingController::reset() {
CoreController::Interrupter interrupter(m_controller);
for (ScriptingTextBuffer* buffer : m_buffers) {
delete buffer;
}
m_buffers.clear();
mScriptContextDetachCore(&m_scriptContext);
mScriptContextDeinit(&m_scriptContext);
m_engines.clear();
m_activeEngine = nullptr;
init();
if (m_controller && m_controller->hasStarted()) {
mScriptContextAttachCore(&m_scriptContext, m_controller->thread()->core);
}
}
void ScriptingController::runCode(const QString& code) {
VFileDevice vf(code.toUtf8());
load(vf, "*prompt");
}
mScriptTextBuffer* ScriptingController::createTextBuffer(void* context) {
ScriptingController* self = static_cast<ScriptingController*>(context);
ScriptingTextBuffer* buffer = new ScriptingTextBuffer(self);
self->m_buffers.append(buffer);
emit self->textBufferCreated(buffer);
return buffer->textBuffer();
}
void ScriptingController::init() {
mScriptContextInit(&m_scriptContext);
mScriptContextAttachStdlib(&m_scriptContext);
mScriptContextRegisterEngines(&m_scriptContext);
mScriptContextAttachLogger(&m_scriptContext, &m_logger);
mScriptContextSetTextBufferFactory(&m_scriptContext, &ScriptingController::createTextBuffer, this);
HashTableEnumerate(&m_scriptContext.engines, [](const char* key, void* engine, void* context) {
ScriptingController* self = static_cast<ScriptingController*>(context);
self->m_engines[QString::fromUtf8(key)] = static_cast<mScriptEngineContext*>(engine);
}, this);
if (m_engines.count() == 1) {
m_activeEngine = *m_engines.begin();
}
}

View File

@ -0,0 +1,67 @@
/* Copyright (c) 2013-2022 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/. */
#pragma once
#include <QHash>
#include <QObject>
#include <mgba/script/context.h>
#include <mgba/core/scripting.h>
#include "VFileDevice.h"
#include <memory>
namespace QGBA {
class CoreController;
class ScriptingTextBuffer;
class ScriptingController : public QObject {
Q_OBJECT
public:
ScriptingController(QObject* parent = nullptr);
~ScriptingController();
void setController(std::shared_ptr<CoreController> controller);
bool loadFile(const QString& path);
bool load(VFileDevice& vf, const QString& name);
mScriptContext* context() { return &m_scriptContext; }
QList<ScriptingTextBuffer*> textBuffers() { return m_buffers; }
signals:
void log(const QString&);
void warn(const QString&);
void error(const QString&);
void textBufferCreated(ScriptingTextBuffer*);
public slots:
void clearController();
void reset();
void runCode(const QString& code);
private:
void init();
static mScriptTextBuffer* createTextBuffer(void* context);
struct Logger : mLogger {
ScriptingController* p;
} m_logger{};
mScriptContext m_scriptContext;
mScriptEngineContext* m_activeEngine = nullptr;
QHash<QString, mScriptEngineContext*> m_engines;
QList<ScriptingTextBuffer*> m_buffers;
std::shared_ptr<CoreController> m_controller;
};
}

View File

@ -0,0 +1,220 @@
/* Copyright (c) 2013-2022 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 "ScriptingTextBuffer.h"
#include "GBAApp.h"
#include <QMutexLocker>
#include <QPlainTextDocumentLayout>
#include <QTextBlock>
using namespace QGBA;
ScriptingTextBuffer::ScriptingTextBuffer(QObject* parent)
: QObject(parent)
{
m_shim.init = &ScriptingTextBuffer::init;
m_shim.deinit = &ScriptingTextBuffer::deinit;
m_shim.setName = &ScriptingTextBuffer::setName;
m_shim.getX = &ScriptingTextBuffer::getX;
m_shim.getY = &ScriptingTextBuffer::getY;
m_shim.cols = &ScriptingTextBuffer::cols;
m_shim.rows = &ScriptingTextBuffer::rows;
m_shim.print = &ScriptingTextBuffer::print;
m_shim.clear = &ScriptingTextBuffer::clear;
m_shim.setSize = &ScriptingTextBuffer::setSize;
m_shim.moveCursor = &ScriptingTextBuffer::moveCursor;
m_shim.advance = &ScriptingTextBuffer::advance;
m_shim.p = this;
m_shim.cursor = QTextCursor(&m_document);
auto layout = new QPlainTextDocumentLayout(&m_document);
m_document.setDocumentLayout(layout);
m_document.setDefaultFont(GBAApp::app()->monospaceFont());
m_document.setMaximumBlockCount(m_dims.height());
QTextOption textOption;
textOption.setWrapMode(QTextOption::NoWrap);
m_document.setDefaultTextOption(textOption);
setBufferName(tr("Untitled buffer"));
}
void ScriptingTextBuffer::setBufferName(const QString& name) {
m_name = name;
m_document.setMetaInformation(QTextDocument::DocumentTitle, name);
emit bufferNameChanged(name);
}
void ScriptingTextBuffer::print(const QString& text) {
QMutexLocker locker(&m_mutex);
QString split(text);
m_shim.cursor.beginEditBlock();
while (m_shim.cursor.positionInBlock() + split.length() > m_dims.width()) {
int cut = m_dims.width() - m_shim.cursor.positionInBlock();
if (!m_shim.cursor.atBlockEnd()) {
m_shim.cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
}
m_shim.cursor.insertText(split.left(cut));
if (m_shim.cursor.atEnd()) {
m_shim.cursor.insertBlock();
} else {
m_shim.cursor.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, 1);
}
split = split.mid(cut);
}
if (!m_shim.cursor.atBlockEnd()) {
m_shim.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, split.length());
}
m_shim.cursor.insertText(split);
m_shim.cursor.endEditBlock();
}
void ScriptingTextBuffer::clear() {
QMutexLocker locker(&m_mutex);
m_document.clear();
m_document.setMetaInformation(QTextDocument::DocumentTitle, m_name);
m_shim.cursor = QTextCursor(&m_document);
}
void ScriptingTextBuffer::setSize(const QSize& size) {
QMutexLocker locker(&m_mutex);
m_dims = size;
m_document.setMaximumBlockCount(m_dims.height());
for (int i = 0; i < m_document.blockCount(); ++i) {
if (m_document.findBlockByNumber(i).length() - 1 > m_dims.width()) {
QTextCursor deleter(m_document.findBlockByNumber(i));
deleter.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, size.width());
deleter.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
deleter.removeSelectedText();
}
}
}
void ScriptingTextBuffer::moveCursor(const QPoint& pos) {
QMutexLocker locker(&m_mutex);
m_shim.cursor.movePosition(QTextCursor::Start);
int y = pos.y();
if (y >= m_dims.height()) {
y = m_dims.height() - 1;
}
if (y >= m_document.blockCount()) {
m_shim.cursor.movePosition(QTextCursor::End);
while (y >= m_document.blockCount()) {
m_shim.cursor.insertBlock();
}
} else {
m_shim.cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, y);
}
int x = pos.x();
if (x >= m_dims.width()) {
x = m_dims.width() - 1;
}
if (x >= m_shim.cursor.block().length()) {
m_shim.cursor.movePosition(QTextCursor::EndOfBlock);
m_shim.cursor.insertText(QString(x - m_shim.cursor.block().length() + 1, QChar(' ')));
} else {
m_shim.cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, x);
}
}
void ScriptingTextBuffer::advance(int increment) {
QMutexLocker locker(&m_mutex);
int x = m_shim.cursor.positionInBlock();
int y = m_shim.cursor.blockNumber();
x += increment;
if (x > 0) {
y += x / m_dims.width();
x %= m_dims.width();
} else if (x < 0) {
y += (x - m_dims.width() + 1) / m_dims.width();
x %= m_dims.width();
if (x) {
x += m_dims.width();
}
}
locker.unlock();
moveCursor({x, y});
}
void ScriptingTextBuffer::init(struct mScriptTextBuffer* buffer, const char* name) {
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
if (name) {
QMetaObject::invokeMethod(self, "setBufferName", Q_ARG(const QString&, QString::fromUtf8(name)));
}
QMetaObject::invokeMethod(self, "clear");
}
void ScriptingTextBuffer::deinit(struct mScriptTextBuffer*) {
// TODO
}
void ScriptingTextBuffer::setName(struct mScriptTextBuffer* buffer, const char* name) {
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
QMetaObject::invokeMethod(self, "setBufferName", Q_ARG(const QString&, QString::fromUtf8(name)));
}
uint32_t ScriptingTextBuffer::getX(const struct mScriptTextBuffer* buffer) {
const ScriptingBufferShim* self = static_cast<const ScriptingTextBuffer::ScriptingBufferShim*>(buffer);
QMutexLocker locker(&self->p->m_mutex);
return self->cursor.positionInBlock();
}
uint32_t ScriptingTextBuffer::getY(const struct mScriptTextBuffer* buffer) {
const ScriptingBufferShim* self = static_cast<const ScriptingTextBuffer::ScriptingBufferShim*>(buffer);
QMutexLocker locker(&self->p->m_mutex);
return self->cursor.blockNumber();
}
uint32_t ScriptingTextBuffer::cols(const struct mScriptTextBuffer* buffer) {
ScriptingTextBuffer* self = static_cast<const ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
QMutexLocker locker(&self->m_mutex);
return self->m_dims.width();
}
uint32_t ScriptingTextBuffer::rows(const struct mScriptTextBuffer* buffer) {
ScriptingTextBuffer* self = static_cast<const ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
QMutexLocker locker(&self->m_mutex);
return self->m_dims.height();
}
void ScriptingTextBuffer::print(struct mScriptTextBuffer* buffer, const char* text) {
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
QMetaObject::invokeMethod(self, "print", Q_ARG(const QString&, QString::fromUtf8(text)));
}
void ScriptingTextBuffer::clear(struct mScriptTextBuffer* buffer) {
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
QMetaObject::invokeMethod(self, "clear");
}
void ScriptingTextBuffer::setSize(struct mScriptTextBuffer* buffer, uint32_t cols, uint32_t rows) {
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
if (cols > 500) {
cols = 500;
}
if (rows > 10000) {
rows = 10000;
}
QMetaObject::invokeMethod(self, "setSize", Q_ARG(QSize, QSize(cols, rows)));
}
void ScriptingTextBuffer::moveCursor(struct mScriptTextBuffer* buffer, uint32_t x, uint32_t y) {
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
if (x > INT_MAX) {
x = INT_MAX;
}
if (y > INT_MAX) {
y = INT_MAX;
}
QMetaObject::invokeMethod(self, "moveCursor", Q_ARG(QPoint, QPoint(x, y)));
}
void ScriptingTextBuffer::advance(struct mScriptTextBuffer* buffer, int32_t adv) {
ScriptingTextBuffer* self = static_cast<ScriptingTextBuffer::ScriptingBufferShim*>(buffer)->p;
emit self->advance(adv);
}

View File

@ -0,0 +1,65 @@
/* Copyright (c) 2013-2022 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/. */
#pragma once
#include <QMutex>
#include <QObject>
#include <QTextCursor>
#include <QTextDocument>
#include <mgba/core/scripting.h>
namespace QGBA {
class ScriptingTextBuffer : public QObject {
Q_OBJECT
public:
ScriptingTextBuffer(QObject* parent);
QTextDocument* document() { return &m_document; };
mScriptTextBuffer* textBuffer() { return &m_shim; }
public slots:
void setBufferName(const QString&);
void print(const QString&);
void clear();
void setSize(const QSize&);
void moveCursor(const QPoint&);
void advance(int);
signals:
void bufferNameChanged(const QString&);
private:
struct ScriptingBufferShim : public mScriptTextBuffer {
ScriptingTextBuffer* p;
QTextCursor cursor;
} m_shim;
QTextDocument m_document;
QMutex m_mutex;
QString m_name;
static void init(struct mScriptTextBuffer*, const char* name);
static void deinit(struct mScriptTextBuffer*);
static void setName(struct mScriptTextBuffer*, const char* name);
static uint32_t getX(const struct mScriptTextBuffer*);
static uint32_t getY(const struct mScriptTextBuffer*);
static uint32_t cols(const struct mScriptTextBuffer*);
static uint32_t rows(const struct mScriptTextBuffer*);
static void print(struct mScriptTextBuffer*, const char* text);
static void clear(struct mScriptTextBuffer*);
static void setSize(struct mScriptTextBuffer*, uint32_t cols, uint32_t rows);
static void moveCursor(struct mScriptTextBuffer*, uint32_t x, uint32_t y);
static void advance(struct mScriptTextBuffer*, int32_t);
QSize m_dims{80, 24};
};
}

View File

@ -0,0 +1,108 @@
/* Copyright (c) 2013-2022 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 "ScriptingView.h"
#include "GBAApp.h"
#include "ConfigController.h"
#include "ScriptingController.h"
#include "ScriptingTextBuffer.h"
using namespace QGBA;
ScriptingView::ScriptingView(ScriptingController* controller, ConfigController* config, QWidget* parent)
: QMainWindow(parent)
, m_config(config)
, m_controller(controller)
{
m_ui.setupUi(this);
m_ui.prompt->setFont(GBAApp::app()->monospaceFont());
m_ui.log->setNewlineTerminated(true);
connect(m_ui.prompt, &QLineEdit::returnPressed, this, &ScriptingView::submitRepl);
connect(m_ui.runButton, &QAbstractButton::clicked, this, &ScriptingView::submitRepl);
connect(m_controller, &ScriptingController::log, m_ui.log, &LogWidget::log);
connect(m_controller, &ScriptingController::warn, m_ui.log, &LogWidget::warn);
connect(m_controller, &ScriptingController::error, m_ui.log, &LogWidget::error);
connect(m_controller, &ScriptingController::textBufferCreated, this, &ScriptingView::addTextBuffer);
connect(m_ui.buffers, &QListWidget::currentRowChanged, this, &ScriptingView::selectBuffer);
connect(m_ui.load, &QAction::triggered, this, &ScriptingView::load);
connect(m_ui.reset, &QAction::triggered, controller, &ScriptingController::reset);
m_mruFiles = m_config->getMRU(ConfigController::MRU::Script);
updateMRU();
for (ScriptingTextBuffer* buffer : controller->textBuffers()) {
addTextBuffer(buffer);
}
}
void ScriptingView::submitRepl() {
m_ui.log->echo(m_ui.prompt->text());
m_controller->runCode(m_ui.prompt->text());
m_ui.prompt->clear();
}
void ScriptingView::load() {
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select script to load"), getFilters());
if (!filename.isEmpty()) {
if (!m_controller->loadFile(filename)) {
return;
}
appendMRU(filename);
}
}
void ScriptingView::addTextBuffer(ScriptingTextBuffer* buffer) {
QTextDocument* document = buffer->document();
m_textBuffers.append(buffer);
QListWidgetItem* item = new QListWidgetItem(document->metaInformation(QTextDocument::DocumentTitle));
connect(buffer, &ScriptingTextBuffer::bufferNameChanged, this, [item](const QString& name) {
item->setText(name);
});
connect(buffer, &QObject::destroyed, this, [this, buffer, item]() {
m_textBuffers.removeAll(buffer);
m_ui.buffers->removeItemWidget(item);
});
m_ui.buffers->addItem(item);
m_ui.buffers->setCurrentItem(item);
}
void ScriptingView::selectBuffer(int index) {
m_ui.buffer->setDocument(m_textBuffers[index]->document());
}
QString ScriptingView::getFilters() const {
QStringList filters;
#ifdef USE_LUA
filters.append(tr("Lua scripts (*.lua)"));
#endif
filters.append(tr("All files (*.*)"));
return filters.join(";;");
}
void ScriptingView::appendMRU(const QString& fname) {
int index = m_mruFiles.indexOf(fname);
if (index >= 0) {
m_mruFiles.removeAt(index);
}
m_mruFiles.prepend(fname);
while (m_mruFiles.size() > ConfigController::MRU_LIST_SIZE) {
m_mruFiles.removeLast();
}
updateMRU();
}
void ScriptingView::updateMRU() {
m_config->setMRU(m_mruFiles, ConfigController::MRU::Script);
m_ui.mru->clear();
for (const auto& fname : m_mruFiles) {
m_ui.mru->addAction(fname, [this, fname]() {
m_controller->loadFile(fname);
});
}
}

View File

@ -0,0 +1,43 @@
/* Copyright (c) 2013-2022 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/. */
#pragma once
#include "ui_ScriptingView.h"
namespace QGBA {
class ConfigController;
class ScriptingController;
class ScriptingTextBuffer;
class ScriptingView : public QMainWindow {
Q_OBJECT
public:
ScriptingView(ScriptingController* controller, ConfigController* config, QWidget* parent = nullptr);
private slots:
void submitRepl();
void load();
void addTextBuffer(ScriptingTextBuffer*);
void selectBuffer(int);
private:
QString getFilters() const;
void appendMRU(const QString&);
void updateMRU();
Ui::ScriptingView m_ui;
ConfigController* m_config;
ScriptingController* m_controller;
QList<ScriptingTextBuffer*> m_textBuffers;
QStringList m_mruFiles;
};
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScriptingView</class>
<widget class="QMainWindow" name="ScriptingView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Scripting</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0">
<item row="0" column="0" rowspan="3">
<widget class="QListWidget" name="buffers">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>180</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QPlainTextEdit" name="buffer">
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QGBA::LogWidget" name="log">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="prompt"/>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="runButton">
<property name="text">
<string>Run</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>29</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<widget class="QMenu" name="mru">
<property name="title">
<string>Load recent script</string>
</property>
</widget>
<addaction name="load"/>
<addaction name="mru"/>
<addaction name="separator"/>
<addaction name="reset"/>
</widget>
<addaction name="menuFile"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="load">
<property name="text">
<string>Load script...</string>
</property>
</action>
<action name="reset">
<property name="text">
<string>&amp;Reset</string>
</property>
</action>
<action name="action0">
<property name="text">
<string>0</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>QGBA::LogWidget</class>
<extends>QPlainTextEdit</extends>
<header>LogWidget.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -6,6 +6,7 @@
#include "SettingsView.h"
#include "AudioProcessor.h"
#include "CheckBoxDelegate.h"
#include "ConfigController.h"
#include "Display.h"
#include "GBAApp.h"
@ -354,9 +355,11 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
}
m_ui.loggingView->setModel(&m_logModel);
m_ui.loggingView->setItemDelegate(new CheckBoxDelegate(m_ui.loggingView));
m_ui.loggingView->setHorizontalHeader(new RotatedHeaderView(Qt::Horizontal));
m_ui.loggingView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
m_ui.loggingView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
connect(m_ui.loggingView, SIGNAL(clicked(QModelIndex)), m_ui.loggingView, SLOT(setCurrentIndex(QModelIndex)));
connect(m_ui.logFileBrowse, &QAbstractButton::pressed, [this] () {
QString path = GBAApp::app()->getSaveFileName(this, "Select log file");

View File

@ -51,6 +51,7 @@
#include "ReportView.h"
#include "ROMInfo.h"
#include "SaveConverter.h"
#include "ScriptingView.h"
#include "SensorView.h"
#include "ShaderSelector.h"
#include "ShortcutController.h"
@ -702,6 +703,19 @@ void Window::consoleOpen() {
}
#endif
#ifdef ENABLE_SCRIPTING
void Window::scriptingOpen() {
if (!m_scripting) {
m_scripting = std::make_unique<ScriptingController>();
if (m_controller) {
m_scripting->setController(m_controller);
}
}
ScriptingView* view = new ScriptingView(m_scripting.get(), m_config);
openView(view);
}
#endif
void Window::resizeEvent(QResizeEvent*) {
if (!isFullScreen()) {
m_config->setOption("height", m_screenWidget->height());
@ -1735,15 +1749,20 @@ void Window::setupMenu(QMenuBar* menubar) {
m_actions.addAction(tr("Settings..."), "settings", this, &Window::openSettingsWindow, "tools")->setRole(Action::Role::SETTINGS);
m_actions.addAction(tr("Make portable"), "makePortable", this, &Window::tryMakePortable, "tools");
#ifdef USE_DEBUGGERS
m_actions.addSeparator("tools");
#ifdef USE_DEBUGGERS
m_actions.addAction(tr("Open debugger console..."), "debuggerWindow", this, &Window::consoleOpen, "tools");
#ifdef USE_GDB_STUB
Action* gdbWindow = addGameAction(tr("Start &GDB server..."), "gdbWindow", this, &Window::gdbOpen, "tools");
m_platformActions.insert(mPLATFORM_GBA, gdbWindow);
#endif
#endif
#ifdef ENABLE_SCRIPTING
m_actions.addAction(tr("Scripting..."), "scripting", this, &Window::scriptingOpen, "tools");
#endif
#if defined(USE_DEBUGGERS) || defined(ENABLE_SCRIPTING)
m_actions.addSeparator("tools");
#endif
addGameAction(tr("View &palette..."), "paletteWindow", openControllerTView<PaletteView>(), "tools");
addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView<ObjView>(), "tools");
@ -2139,6 +2158,12 @@ void Window::setController(CoreController* controller, const QString& fname) {
m_pendingPatch = QString();
}
#ifdef ENABLE_SCRIPTING
if (m_scripting) {
m_scripting->setController(m_controller);
}
#endif
attachDisplay();
m_controller->loadConfig(m_config);
m_config->updateOption("showOSD");

View File

@ -23,6 +23,9 @@
#include "LoadSaveState.h"
#include "LogController.h"
#include "SettingsView.h"
#ifdef ENABLE_SCRIPTING
#include "ScriptingController.h"
#endif
namespace QGBA {
@ -113,6 +116,10 @@ public slots:
void gdbOpen();
#endif
#ifdef ENABLE_SCRIPTING
void scriptingOpen();
#endif
protected:
virtual void resizeEvent(QResizeEvent*) override;
virtual void showEvent(QShowEvent*) override;
@ -213,7 +220,7 @@ private:
QTimer m_fpsTimer;
QTimer m_mustRestart;
QTimer m_mustReset;
QList<QString> m_mruFiles;
QStringList m_mruFiles;
ShortcutController* m_shortcutController;
#if defined(BUILD_GL) || defined(BUILD_GLES2)
std::unique_ptr<ShaderSelector> m_shaderView;
@ -250,6 +257,10 @@ private:
#ifdef USE_SQLITE3
LibraryController* m_libraryView;
#endif
#ifdef ENABLE_SCRIPTING
std::unique_ptr<ScriptingController> m_scripting;
#endif
};
class WindowBackground : public QWidget {

20
src/script/CMakeLists.txt Normal file
View File

@ -0,0 +1,20 @@
include(ExportDirectory)
set(SOURCE_FILES
context.c
stdlib.c
types.c)
set(TEST_FILES
test/classes.c
test/types.c)
if(USE_LUA)
list(APPEND SOURCE_FILES engines/lua.c)
list(APPEND TEST_FILES test/lua.c)
endif()
source_group("Scripting" FILES ${SOURCE_FILES})
source_group("Scripting tests" FILES ${TEST_FILES})
export_directory(SCRIPT SOURCE_FILES)
export_directory(SCRIPT_TEST TEST_FILES)

273
src/script/context.c Normal file
View File

@ -0,0 +1,273 @@
/* Copyright (c) 2013-2022 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/context.h>
#ifdef USE_LUA
#include <mgba/internal/script/lua.h>
#endif
struct mScriptFileInfo {
const char* name;
struct VFile* vf;
struct mScriptEngineContext* context;
};
static void _engineContextDestroy(void* ctx) {
struct mScriptEngineContext* context = ctx;
context->destroy(context);
}
static void _engineAddGlobal(const char* key, void* value, void* user) {
struct mScriptEngineContext* context = user;
context->setGlobal(context, key, value);
}
static void _contextAddGlobal(const char* key, void* value, void* user) {
UNUSED(key);
struct mScriptEngineContext* context = value;
struct mScriptKVPair* pair = user;
context->setGlobal(context, pair->key, pair->value);
}
static void _contextRemoveGlobal(const char* key, void* value, void* user) {
UNUSED(key);
struct mScriptEngineContext* context = value;
context->setGlobal(context, user, NULL);
}
static void _contextFindForFile(const char* key, void* value, void* user) {
UNUSED(key);
struct mScriptFileInfo* info = user;
struct mScriptEngineContext* context = value;
if (info->context) {
return;
}
if (context->isScript(context, info->name, info->vf)) {
info->context = context;
}
}
void mScriptContextInit(struct mScriptContext* context) {
HashTableInit(&context->rootScope, 0, (void (*)(void*)) mScriptValueDeref);
HashTableInit(&context->engines, 0, _engineContextDestroy);
mScriptListInit(&context->refPool, 0);
TableInit(&context->weakrefs, 0, (void (*)(void*)) mScriptValueDeref);
context->nextWeakref = 1;
HashTableInit(&context->callbacks, 0, (void (*)(void*)) mScriptValueDeref);
context->constants = NULL;
}
void mScriptContextDeinit(struct mScriptContext* context) {
HashTableDeinit(&context->rootScope);
HashTableDeinit(&context->weakrefs);
mScriptContextDrainPool(context);
mScriptListDeinit(&context->refPool);
HashTableDeinit(&context->callbacks);
HashTableDeinit(&context->engines);
}
void mScriptContextFillPool(struct mScriptContext* context, struct mScriptValue* value) {
if (value->refs == mSCRIPT_VALUE_UNREF) {
return;
}
switch (value->type->base) {
case mSCRIPT_TYPE_SINT:
case mSCRIPT_TYPE_UINT:
case mSCRIPT_TYPE_FLOAT:
return;
default:
break;
}
struct mScriptValue* poolEntry = mScriptListAppend(&context->refPool);
poolEntry->type = mSCRIPT_TYPE_MS_WRAPPER;
poolEntry->value.opaque = value;
poolEntry->refs = mSCRIPT_VALUE_UNREF;
}
void mScriptContextDrainPool(struct mScriptContext* context) {
size_t i;
for (i = 0; i < mScriptListSize(&context->refPool); ++i) {
struct mScriptValue* value = mScriptValueUnwrap(mScriptListGetPointer(&context->refPool, i));
if (value) {
mScriptValueDeref(value);
}
}
mScriptListClear(&context->refPool);
}
struct mScriptEngineContext* mScriptContextRegisterEngine(struct mScriptContext* context, struct mScriptEngine2* engine) {
struct mScriptEngineContext* ectx = engine->create(engine, context);
if (ectx) {
HashTableInsert(&context->engines, engine->name, ectx);
HashTableEnumerate(&context->rootScope, _engineAddGlobal, ectx);
}
return ectx;
}
void mScriptContextRegisterEngines(struct mScriptContext* context) {
UNUSED(context);
#ifdef USE_LUA
mScriptContextRegisterEngine(context, mSCRIPT_ENGINE_LUA);
#endif
}
void mScriptContextSetGlobal(struct mScriptContext* context, const char* key, struct mScriptValue* value) {
struct mScriptValue* oldValue = HashTableLookup(&context->rootScope, key);
if (oldValue) {
mScriptContextClearWeakref(context, oldValue->value.u32);
}
value = mScriptContextMakeWeakref(context, value);
HashTableInsert(&context->rootScope, key, value);
struct mScriptKVPair pair = {
.key = key,
.value = value
};
HashTableEnumerate(&context->engines, _contextAddGlobal, &pair);
}
struct mScriptValue* mScriptContextGetGlobal(struct mScriptContext* context, const char* key) {
struct mScriptValue* weakref = HashTableLookup(&context->rootScope, key);
if (!weakref) {
return NULL;
}
return mScriptContextAccessWeakref(context, weakref);
}
void mScriptContextRemoveGlobal(struct mScriptContext* context, const char* key) {
if (!HashTableLookup(&context->rootScope, key)) {
return;
}
// Since _contextRemoveGlobal doesn't mutate |key|, this cast should be safe
HashTableEnumerate(&context->engines, _contextRemoveGlobal, (char*) key);
struct mScriptValue* oldValue = HashTableLookup(&context->rootScope, key);
if (oldValue) {
mScriptContextClearWeakref(context, oldValue->value.u32);
HashTableRemove(&context->rootScope, key);
}
}
struct mScriptValue* mScriptContextEnsureGlobal(struct mScriptContext* context, const char* key, const struct mScriptType* type) {
struct mScriptValue* value = mScriptContextGetGlobal(context, key);
if (!value) {
mScriptContextSetGlobal(context, key, mScriptValueAlloc(type));
value = mScriptContextGetGlobal(context, key);
}
return value;
}
uint32_t mScriptContextSetWeakref(struct mScriptContext* context, struct mScriptValue* value) {
mScriptValueRef(value);
TableInsert(&context->weakrefs, context->nextWeakref, value);
uint32_t nextWeakref = context->nextWeakref;
++context->nextWeakref;
while (TableLookup(&context->weakrefs, context->nextWeakref)) {
++context->nextWeakref;
}
return nextWeakref;
}
struct mScriptValue* mScriptContextMakeWeakref(struct mScriptContext* context, struct mScriptValue* value) {
uint32_t weakref = mScriptContextSetWeakref(context, value);
mScriptValueDeref(value);
value = mScriptValueAlloc(mSCRIPT_TYPE_MS_WEAKREF);
value->value.u32 = weakref;
return value;
}
struct mScriptValue* mScriptContextAccessWeakref(struct mScriptContext* context, struct mScriptValue* value) {
if (value->type != mSCRIPT_TYPE_MS_WEAKREF) {
return value;
}
return TableLookup(&context->weakrefs, value->value.u32);
}
void mScriptContextClearWeakref(struct mScriptContext* context, uint32_t weakref) {
TableRemove(&context->weakrefs, weakref);
}
void mScriptContextTriggerCallback(struct mScriptContext* context, const char* callback) {
struct mScriptValue* list = HashTableLookup(&context->callbacks, callback);
if (!list) {
return;
}
size_t i;
for (i = 0; i < mScriptListSize(list->value.list); ++i) {
struct mScriptFrame frame;
mScriptFrameInit(&frame);
struct mScriptValue* fn = mScriptListGetPointer(list->value.list, i);
if (fn->type->base == mSCRIPT_TYPE_WRAPPER) {
fn = mScriptValueUnwrap(fn);
}
mScriptInvoke(fn, &frame);
mScriptFrameDeinit(&frame);
}
}
void mScriptContextAddCallback(struct mScriptContext* context, const char* callback, struct mScriptValue* fn) {
if (fn->type->base != mSCRIPT_TYPE_FUNCTION) {
return;
}
struct mScriptValue* list = HashTableLookup(&context->callbacks, callback);
if (!list) {
list = mScriptValueAlloc(mSCRIPT_TYPE_MS_LIST);
HashTableInsert(&context->callbacks, callback, list);
}
mScriptValueWrap(fn, mScriptListAppend(list->value.list));
}
void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants) {
if (!context->constants) {
context->constants = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
}
struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
size_t i;
for (i = 0; constants[i].key; ++i) {
struct mScriptValue* key = mScriptStringCreateFromUTF8(constants[i].key);
mScriptTableInsert(table, key, constants[i].value);
mScriptValueDeref(key);
mScriptValueDeref(constants[i].value);
}
struct mScriptValue* key = mScriptStringCreateFromUTF8(nspace);
mScriptTableInsert(context->constants, key, table);
mScriptValueDeref(key);
mScriptValueDeref(table);
}
bool mScriptContextLoadVF(struct mScriptContext* context, const char* name, struct VFile* vf) {
struct mScriptFileInfo info = {
.name = name,
.vf = vf,
.context = NULL
};
HashTableEnumerate(&context->engines, _contextFindForFile, &info);
if (!info.context) {
return false;
}
return info.context->load(info.context, name, vf);
}
bool mScriptContextLoadFile(struct mScriptContext* context, const char* path) {
struct VFile* vf = VFileOpen(path, O_RDONLY);
if (!vf) {
return false;
}
bool ret = mScriptContextLoadVF(context, path, vf);
vf->close(vf);
return ret;
}
bool mScriptInvoke(const struct mScriptValue* val, struct mScriptFrame* frame) {
if (val->type->base != mSCRIPT_TYPE_FUNCTION) {
return false;
}
const struct mScriptTypeFunction* signature = &val->type->details.function;
if (!mScriptCoerceFrame(&signature->parameters, &frame->arguments)) {
return false;
}
const struct mScriptFunction* fn = val->value.opaque;
return fn->call(frame, fn->context);
}

497
src/script/docgen.c Normal file
View File

@ -0,0 +1,497 @@
/* Copyright (c) 2013-2022 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/core/core.h>
#include <mgba/core/scripting.h>
#include <mgba/core/version.h>
#include <mgba/script/context.h>
struct mScriptContext context;
struct Table types;
FILE* out;
void explainValue(struct mScriptValue* value, int level);
void explainType(struct mScriptType* type, int level);
void addTypesFromTuple(const struct mScriptTypeTuple*);
void addTypesFromTable(struct Table*);
void addType(const struct mScriptType* type) {
if (HashTableLookup(&types, type->name) || type->isConst) {
return;
}
HashTableInsert(&types, type->name, (struct mScriptType*) type);
switch (type->base) {
case mSCRIPT_TYPE_FUNCTION:
addTypesFromTuple(&type->details.function.parameters);
addTypesFromTuple(&type->details.function.returnType);
break;
case mSCRIPT_TYPE_OBJECT:
mScriptClassInit(type->details.cls);
if (type->details.cls->parent) {
addType(type->details.cls->parent);
}
addTypesFromTable(&type->details.cls->instanceMembers);
break;
case mSCRIPT_TYPE_OPAQUE:
case mSCRIPT_TYPE_WRAPPER:
if (type->details.type) {
addType(type->details.type);
}
case mSCRIPT_TYPE_VOID:
case mSCRIPT_TYPE_SINT:
case mSCRIPT_TYPE_UINT:
case mSCRIPT_TYPE_FLOAT:
case mSCRIPT_TYPE_STRING:
case mSCRIPT_TYPE_LIST:
case mSCRIPT_TYPE_TABLE:
case mSCRIPT_TYPE_WEAKREF:
// No subtypes
break;
}
}
void addTypesFromTuple(const struct mScriptTypeTuple* tuple) {
size_t i;
for (i = 0; i < tuple->count; ++i) {
addType(tuple->entries[i]);
}
}
void addTypesFromTable(struct Table* table) {
struct TableIterator iter;
if (!HashTableIteratorStart(table, &iter)) {
return;
}
do {
struct mScriptClassMember* member = HashTableIteratorGetValue(table, &iter);
addType(member->type);
} while(HashTableIteratorNext(table, &iter));
}
void printchomp(const char* string, int level) {
char indent[(level + 1) * 2 + 1];
memset(indent, ' ', sizeof(indent) - 1);
indent[sizeof(indent) - 1] = '\0';
const char* start = string;
char lineBuffer[1024];
while (true) {
const char* end = strchr(start, '\n');
if (end) {
size_t size = end - start;
if (sizeof(lineBuffer) - 1 < size) {
size = sizeof(lineBuffer) - 1;
}
strncpy(lineBuffer, start, size);
lineBuffer[size] = '\0';
fprintf(out, "%s%s\n", indent, lineBuffer);
} else {
fprintf(out, "%s%s\n", indent, start);
break;
}
start = end + 1;
if (!*end) {
break;
}
}
}
bool printval(const struct mScriptValue* value, char* buffer, size_t bufferSize) {
struct mScriptValue sval;
switch (value->type->base) {
case mSCRIPT_TYPE_SINT:
if (value->type->size <= 4) {
snprintf(buffer, bufferSize, "%"PRId32, value->value.s32);
return true;
}
if (value->type->size == 8) {
snprintf(buffer, bufferSize, "%"PRId64, value->value.s64);
return true;
}
return false;
case mSCRIPT_TYPE_UINT:
if (value->type->size <= 4) {
snprintf(buffer, bufferSize, "%"PRIu32, value->value.u32);
return true;
}
if (value->type->size == 8) {
snprintf(buffer, bufferSize, "%"PRIu64, value->value.u64);
return true;
}
return false;
case mSCRIPT_TYPE_FLOAT:
if (value->type->size <= 4) {
snprintf(buffer, bufferSize, "%g", value->value.f32);
return true;
}
if (value->type->size == 8) {
snprintf(buffer, bufferSize, "%g", value->value.f64);
return true;
}
return false;
case mSCRIPT_TYPE_STRING:
if (!mScriptCast(mSCRIPT_TYPE_MS_CHARP, value, &sval)) {
return false;
}
if (sval.value.copaque) {
snprintf(buffer, bufferSize, "\"%s\"", (const char*) sval.value.copaque);
} else {
snprintf(buffer, bufferSize, "null");
}
return true;
case mSCRIPT_TYPE_VOID:
snprintf(buffer, bufferSize, "null");
return true;
case mSCRIPT_TYPE_OPAQUE:
case mSCRIPT_TYPE_LIST:
case mSCRIPT_TYPE_TABLE:
// Not scalar or string values
return false;
}
return false;
}
void explainTable(struct mScriptValue* value, int level) {
char indent[(level + 1) * 2 + 1];
memset(indent, ' ', sizeof(indent) - 1);
indent[sizeof(indent) - 1] = '\0';
struct TableIterator iter;
if (mScriptTableIteratorStart(value, &iter)) {
do {
char keyval[1024];
struct mScriptValue* k = mScriptTableIteratorGetKey(value, &iter);
printval(k, keyval, sizeof(keyval));
fprintf(out, "%s- key: %s\n", indent, keyval);
struct mScriptValue* v = mScriptTableIteratorGetValue(value, &iter);
explainValue(v, level + 1);
} while (mScriptTableIteratorNext(value, &iter));
}
}
void explainClass(struct mScriptTypeClass* cls, int level) {
char indent[(level + 1) * 2 + 1];
memset(indent, ' ', sizeof(indent) - 1);
indent[sizeof(indent) - 1] = '\0';
if (cls->parent) {
fprintf(out, "%sparent: %s\n", indent, cls->parent->name);
}
if (cls->docstring) {
if (strchr(cls->docstring, '\n')) {
fprintf(out, "%scomment: |-\n", indent);
printchomp(cls->docstring, level + 1);
} else {
fprintf(out, "%scomment: \"%s\"\n", indent, cls->docstring);
}
}
fprintf(out, "%smembers:\n", indent);
const char* docstring = NULL;
const struct mScriptClassInitDetails* details;
size_t i;
for (i = 0; cls->details[i].type != mSCRIPT_CLASS_INIT_END; ++i) {
details = &cls->details[i];
switch (details->type) {
case mSCRIPT_CLASS_INIT_DOCSTRING:
docstring = details->info.comment;
break;
case mSCRIPT_CLASS_INIT_INSTANCE_MEMBER:
fprintf(out, "%s %s:\n", indent, details->info.member.name);
if (docstring) {
fprintf(out, "%s comment: \"%s\"\n", indent, docstring);
docstring = NULL;
}
fprintf(out, "%s type: %s\n", indent, details->info.member.type->name);
break;
case mSCRIPT_CLASS_INIT_END:
break;
}
}
}
void explainObject(struct mScriptValue* value, int level) {
char indent[(level + 2) * 2 + 1];
memset(indent, ' ', sizeof(indent) - 1);
indent[sizeof(indent) - 1] = '\0';
struct mScriptTypeClass* cls = value->type->details.cls;
const struct mScriptClassInitDetails* details;
size_t i;
for (i = 0; cls->details[i].type != mSCRIPT_CLASS_INIT_END; ++i) {
struct mScriptValue member;
details = &cls->details[i];
if (cls->details[i].type != mSCRIPT_CLASS_INIT_INSTANCE_MEMBER) {
continue;
}
fprintf(out, "%s%s:\n", indent, details->info.member.name);
addType(details->info.member.type);
if (mScriptObjectGet(value, details->info.member.name, &member)) {
struct mScriptValue* unwrappedMember;
if (member.type->base == mSCRIPT_TYPE_WRAPPER) {
unwrappedMember = mScriptValueUnwrap(&member);
explainValue(unwrappedMember, level + 2);
} else {
explainValue(&member, level + 2);
}
}
}
}
void explainValue(struct mScriptValue* value, int level) {
char valstring[1024];
char indent[(level + 1) * 2 + 1];
memset(indent, ' ', sizeof(indent) - 1);
indent[sizeof(indent) - 1] = '\0';
value = mScriptContextAccessWeakref(&context, value);
addType(value->type);
fprintf(out, "%stype: %s\n", indent, value->type->name);
switch (value->type->base) {
case mSCRIPT_TYPE_TABLE:
fprintf(out, "%svalue:\n", indent);
explainTable(value, level);
break;
case mSCRIPT_TYPE_SINT:
case mSCRIPT_TYPE_UINT:
case mSCRIPT_TYPE_STRING:
printval(value, valstring, sizeof(valstring));
fprintf(out, "%svalue: %s\n", indent, valstring);
break;
case mSCRIPT_TYPE_OBJECT:
fprintf(out, "%svalue:\n", indent);
explainObject(value, level);
break;
default:
break;
}
}
void explainTypeTuple(struct mScriptTypeTuple* tuple, int level) {
char indent[(level + 1) * 2 + 1];
memset(indent, ' ', sizeof(indent) - 1);
indent[sizeof(indent) - 1] = '\0';
fprintf(out, "%svariable: %s\n", indent, tuple->variable ? "yes" : "no");
fprintf(out, "%slist:\n", indent);
size_t i;
for (i = 0; i < tuple->count; ++i) {
if (tuple->names[i]) {
fprintf(out, "%s- name: %s\n", indent, tuple->names[i]);
fprintf(out, "%s type: %s\n", indent, tuple->entries[i]->name);
} else {
fprintf(out, "%s- type: %s\n", indent, tuple->entries[i]->name);
}
if (tuple->defaults && tuple->defaults[i].type) {
char defaultValue[128];
printval(&tuple->defaults[i], defaultValue, sizeof(defaultValue));
fprintf(out, "%s default: %s\n", indent, defaultValue);
}
}
}
void explainType(struct mScriptType* type, int level) {
char indent[(level + 1) * 2 + 1];
memset(indent, ' ', sizeof(indent) - 1);
indent[sizeof(indent) - 1] = '\0';
fprintf(out, "%sbase: ", indent);
switch (type->base) {
case mSCRIPT_TYPE_SINT:
fputs("sint\n", out);
break;
case mSCRIPT_TYPE_UINT:
fputs("uint\n", out);
break;
case mSCRIPT_TYPE_FLOAT:
fputs("float\n", out);
break;
case mSCRIPT_TYPE_STRING:
fputs("string\n", out);
break;
case mSCRIPT_TYPE_FUNCTION:
fputs("function\n", out);
fprintf(out, "%sparameters:\n", indent);
explainTypeTuple(&type->details.function.parameters, level + 1);
fprintf(out, "%sreturn:\n", indent);
explainTypeTuple(&type->details.function.returnType, level + 1);
break;
case mSCRIPT_TYPE_OPAQUE:
fputs("opaque\n", out);
break;
case mSCRIPT_TYPE_OBJECT:
fputs("object\n", out);
explainClass(type->details.cls, level);
break;
case mSCRIPT_TYPE_LIST:
fputs("list\n", out);
break;
case mSCRIPT_TYPE_TABLE:
fputs("table\n", out);
break;
case mSCRIPT_TYPE_WRAPPER:
fputs("wrapper\n", out);
break;
case mSCRIPT_TYPE_WEAKREF:
fputs("weakref\n", out);
break;
case mSCRIPT_TYPE_VOID:
fputs("void\n", out);
break;
}
}
bool call(struct mScriptValue* obj, const char* method, struct mScriptFrame* frame) {
struct mScriptValue fn;
if (!mScriptObjectGet(obj, method, &fn)) {
return false;
}
struct mScriptValue* this = mScriptListAppend(&frame->arguments);
this->type = mSCRIPT_TYPE_MS_WRAPPER;
this->refs = mSCRIPT_VALUE_UNREF;
this->flags = 0;
this->value.opaque = obj;
return mScriptInvoke(&fn, frame);
}
void explainCore(struct mCore* core) {
struct mScriptValue wrapper;
size_t size;
size_t i;
mScriptContextAttachCore(&context, core);
struct mScriptValue* emu = mScriptContextGetGlobal(&context, "emu");
addType(emu->type);
if (mScriptObjectGet(emu, "memory", &wrapper)) {
struct mScriptValue* memory = mScriptValueUnwrap(&wrapper);
struct TableIterator iter;
fputs(" memory:\n", out);
if (mScriptTableIteratorStart(memory, &iter)) {
do {
struct mScriptValue* name = mScriptTableIteratorGetKey(memory, &iter);
struct mScriptValue* value = mScriptTableIteratorGetValue(memory, &iter);
fprintf(out, " %s:\n", name->value.string->buffer);
value = mScriptContextAccessWeakref(&context, value);
struct mScriptFrame frame;
uint32_t baseVal;
struct mScriptValue* shortName;
mScriptFrameInit(&frame);
call(value, "base", &frame);
mScriptPopU32(&frame.returnValues, &baseVal);
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
call(value, "name", &frame);
shortName = mScriptValueUnwrap(mScriptListGetPointer(&frame.returnValues, 0));
mScriptFrameDeinit(&frame);
fprintf(out, " base: 0x%x\n", baseVal);
fprintf(out, " name: \"%s\"\n", shortName->value.string->buffer);
mScriptValueDeref(shortName);
} while (mScriptTableIteratorNext(memory, &iter));
}
}
const struct mCoreRegisterInfo* registers;
size = core->listRegisters(core, &registers);
if (size) {
fputs(" registers:\n", out);
for (i = 0; i < size; ++i) {
if (strncmp(registers[i].name, "spsr", 4) == 0) {
// SPSR access is not implemented yet
continue;
}
fprintf(out, " - name: \"%s\"\n", registers[i].name);
if (registers[i].aliases && registers[i].aliases[0]) {
size_t alias;
fputs(" aliases:\n", out);
for (alias = 0; registers[i].aliases[alias]; ++alias) {
fprintf(out, " - \"%s\"\n", registers[i].aliases[alias]);
}
}
fprintf(out, " width: %u\n", registers[i].width);
}
}
mScriptContextDetachCore(&context);
}
int main(int argc, char* argv[]) {
out = stdout;
if (argc > 1) {
out = fopen(argv[1], "w");
if (!out) {
perror("Couldn't open output");
return 1;
}
}
mScriptContextInit(&context);
mScriptContextAttachStdlib(&context);
mScriptContextSetTextBufferFactory(&context, NULL, NULL);
HashTableInit(&types, 0, NULL);
addType(mSCRIPT_TYPE_MS_S8);
addType(mSCRIPT_TYPE_MS_U8);
addType(mSCRIPT_TYPE_MS_S16);
addType(mSCRIPT_TYPE_MS_U16);
addType(mSCRIPT_TYPE_MS_S32);
addType(mSCRIPT_TYPE_MS_U32);
addType(mSCRIPT_TYPE_MS_F32);
addType(mSCRIPT_TYPE_MS_S64);
addType(mSCRIPT_TYPE_MS_U64);
addType(mSCRIPT_TYPE_MS_F64);
addType(mSCRIPT_TYPE_MS_STR);
addType(mSCRIPT_TYPE_MS_CHARP);
addType(mSCRIPT_TYPE_MS_LIST);
addType(mSCRIPT_TYPE_MS_TABLE);
addType(mSCRIPT_TYPE_MS_WRAPPER);
fputs("version:\n", out);
fprintf(out, " string: \"%s\"\n", projectVersion);
fprintf(out, " commit: \"%s\"\n", gitCommit);
fputs("root:\n", out);
struct TableIterator iter;
if (HashTableIteratorStart(&context.rootScope, &iter)) {
do {
const char* name = HashTableIteratorGetKey(&context.rootScope, &iter);
fprintf(out, " %s:\n", name);
struct mScriptValue* value = HashTableIteratorGetValue(&context.rootScope, &iter);
explainValue(value, 1);
} while (HashTableIteratorNext(&context.rootScope, &iter));
}
fputs("emu:\n", out);
struct mCore* core;
core = mCoreCreate(mPLATFORM_GBA);
if (core) {
fputs(" gba:\n", out);
core->init(core);
explainCore(core);
core->deinit(core);
}
core = mCoreCreate(mPLATFORM_GB);
if (core) {
fputs(" gb:\n", out);
core->init(core);
explainCore(core);
core->deinit(core);
}
fputs("types:\n", out);
if (HashTableIteratorStart(&types, &iter)) {
do {
const char* name = HashTableIteratorGetKey(&types, &iter);
fprintf(out, " %s:\n", name);
struct mScriptType* type = HashTableIteratorGetValue(&types, &iter);
explainType(type, 1);
} while (HashTableIteratorNext(&types, &iter));
}
HashTableDeinit(&types);
mScriptContextDeinit(&context);
return 0;
}

872
src/script/engines/lua.c Normal file
View File

@ -0,0 +1,872 @@
/* Copyright (c) 2013-2022 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/internal/script/lua.h>
#include <mgba/script/macros.h>
#include <mgba-util/string.h>
#include <lualib.h>
#include <lauxlib.h>
#define MAX_KEY_SIZE 128
static struct mScriptEngineContext* _luaCreate(struct mScriptEngine2*, struct mScriptContext*);
static void _luaDestroy(struct mScriptEngineContext*);
static bool _luaIsScript(struct mScriptEngineContext*, const char*, struct VFile*);
static struct mScriptValue* _luaGetGlobal(struct mScriptEngineContext*, const char* name);
static bool _luaSetGlobal(struct mScriptEngineContext*, const char* name, struct mScriptValue*);
static bool _luaLoad(struct mScriptEngineContext*, const char*, struct VFile*);
static bool _luaRun(struct mScriptEngineContext*);
static const char* _luaGetError(struct mScriptEngineContext*);
static bool _luaCall(struct mScriptFrame*, void* context);
struct mScriptEngineContextLua;
static bool _luaPushFrame(struct mScriptEngineContextLua*, struct mScriptList*, bool internal);
static bool _luaPopFrame(struct mScriptEngineContextLua*, struct mScriptList*);
static bool _luaInvoke(struct mScriptEngineContextLua*, struct mScriptFrame*);
static struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext);
static bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue*);
static void _luaDeref(struct mScriptValue*);
static int _luaThunk(lua_State* lua);
static int _luaGetObject(lua_State* lua);
static int _luaSetObject(lua_State* lua);
static int _luaGcObject(lua_State* lua);
static int _luaGetTable(lua_State* lua);
static int _luaLenTable(lua_State* lua);
static int _luaPairsTable(lua_State* lua);
static int _luaGetList(lua_State* lua);
static int _luaLenList(lua_State* lua);
#if LUA_VERSION_NUM < 503
#define lua_pushinteger lua_pushnumber
#endif
const struct mScriptType mSTLuaFunc = {
.base = mSCRIPT_TYPE_FUNCTION,
.size = 0,
.name = "lua-" LUA_VERSION_ONLY "::function",
.details = {
.function = {
.parameters = {
.variable = true
},
.returnType = {
.variable = true
}
}
},
.alloc = NULL,
.free = _luaDeref,
.hash = NULL,
.equal = NULL,
.cast = NULL,
};
struct mScriptEngineContextLua {
struct mScriptEngineContext d;
lua_State* lua;
int func;
char* lastError;
};
struct mScriptEngineContextLuaRef {
struct mScriptEngineContextLua* context;
int ref;
};
static struct mScriptEngineLua {
struct mScriptEngine2 d;
} _engineLua = {
.d = {
.name = "lua-" LUA_VERSION_ONLY,
.init = NULL,
.deinit = NULL,
.create = _luaCreate
}
};
struct mScriptEngine2* const mSCRIPT_ENGINE_LUA = &_engineLua.d;
static const luaL_Reg _mSTStruct[] = {
{ "__index", _luaGetObject },
{ "__newindex", _luaSetObject },
{ "__gc", _luaGcObject },
{ NULL, NULL }
};
static const luaL_Reg _mSTTable[] = {
{ "__index", _luaGetTable },
{ "__len", _luaLenTable },
{ "__pairs", _luaPairsTable },
{ NULL, NULL }
};
static const luaL_Reg _mSTList[] = {
{ "__index", _luaGetList },
{ "__len", _luaLenList },
{ "__gc", _luaGcObject },
{ NULL, NULL }
};
struct mScriptEngineContext* _luaCreate(struct mScriptEngine2* engine, struct mScriptContext* context) {
UNUSED(engine);
struct mScriptEngineContextLua* luaContext = calloc(1, sizeof(*luaContext));
luaContext->d = (struct mScriptEngineContext) {
.context = context,
.destroy = _luaDestroy,
.isScript = _luaIsScript,
.getGlobal = _luaGetGlobal,
.setGlobal = _luaSetGlobal,
.load = _luaLoad,
.run = _luaRun,
.getError = _luaGetError
};
luaContext->lua = luaL_newstate();
luaContext->func = -1;
luaL_openlibs(luaContext->lua);
luaL_newmetatable(luaContext->lua, "mSTStruct");
#if LUA_VERSION_NUM < 502
luaL_register(luaContext->lua, NULL, _mSTStruct);
#else
luaL_setfuncs(luaContext->lua, _mSTStruct, 0);
#endif
lua_pop(luaContext->lua, 1);
luaL_newmetatable(luaContext->lua, "mSTTable");
#if LUA_VERSION_NUM < 502
luaL_register(luaContext->lua, NULL, _mSTTable);
#else
luaL_setfuncs(luaContext->lua, _mSTTable, 0);
#endif
lua_pop(luaContext->lua, 1);
luaL_newmetatable(luaContext->lua, "mSTList");
#if LUA_VERSION_NUM < 502
luaL_register(luaContext->lua, NULL, _mSTList);
#else
luaL_setfuncs(luaContext->lua, _mSTList, 0);
#endif
lua_pop(luaContext->lua, 1);
return &luaContext->d;
}
void _luaDestroy(struct mScriptEngineContext* ctx) {
struct mScriptEngineContextLua* luaContext = (struct mScriptEngineContextLua*) ctx;
if (luaContext->lastError) {
free(luaContext->lastError);
luaContext->lastError = NULL;
}
if (luaContext->func > 0) {
luaL_unref(luaContext->lua, LUA_REGISTRYINDEX, luaContext->func);
}
lua_close(luaContext->lua);
free(luaContext);
}
bool _luaIsScript(struct mScriptEngineContext* ctx, const char* name, struct VFile* vf) {
UNUSED(ctx);
UNUSED(vf);
if (!name) {
return false;
}
return endswith(name, ".lua");
}
struct mScriptValue* _luaGetGlobal(struct mScriptEngineContext* ctx, const char* name) {
struct mScriptEngineContextLua* luaContext = (struct mScriptEngineContextLua*) ctx;
lua_getglobal(luaContext->lua, name);
return _luaCoerce(luaContext);
}
bool _luaSetGlobal(struct mScriptEngineContext* ctx, const char* name, struct mScriptValue* value) {
struct mScriptEngineContextLua* luaContext = (struct mScriptEngineContextLua*) ctx;
if (!value) {
lua_pushnil(luaContext->lua);
} else if (!_luaWrap(luaContext, value)) {
return false;
}
lua_setglobal(luaContext->lua, name);
return true;
}
struct mScriptValue* _luaCoerceFunction(struct mScriptEngineContextLua* luaContext) {
struct mScriptValue* value = mScriptValueAlloc(&mSTLuaFunc);
struct mScriptFunction* fn = calloc(1, sizeof(*fn));
struct mScriptEngineContextLuaRef* ref = calloc(1, sizeof(*ref));
fn->call = _luaCall;
fn->context = ref;
ref->context = luaContext;
ref->ref = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX);
value->value.opaque = fn;
return value;
}
struct mScriptValue* _luaCoerce(struct mScriptEngineContextLua* luaContext) {
if (lua_isnone(luaContext->lua, -1)) {
lua_pop(luaContext->lua, 1);
return NULL;
}
size_t size;
const void* buffer;
struct mScriptValue* value = NULL;
switch (lua_type(luaContext->lua, -1)) {
case LUA_TNUMBER:
#if LUA_VERSION_NUM >= 503
if (lua_isinteger(luaContext->lua, -1)) {
value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S64);
value->value.s64 = lua_tointeger(luaContext->lua, -1);
break;
}
#endif
value = mScriptValueAlloc(mSCRIPT_TYPE_MS_F64);
value->value.f64 = lua_tonumber(luaContext->lua, -1);
break;
case LUA_TBOOLEAN:
value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32);
value->value.s32 = lua_toboolean(luaContext->lua, -1);
break;
case LUA_TSTRING:
buffer = lua_tolstring(luaContext->lua, -1, &size);
value = mScriptStringCreateFromBytes(buffer, size);
mScriptContextFillPool(luaContext->d.context, value);
break;
case LUA_TFUNCTION:
// This function pops the value internally via luaL_ref
return _luaCoerceFunction(luaContext);
case LUA_TUSERDATA:
if (!lua_getmetatable(luaContext->lua, -1)) {
break;
}
luaL_getmetatable(luaContext->lua, "mSTStruct");
if (!lua_rawequal(luaContext->lua, -1, -2)) {
lua_pop(luaContext->lua, 2);
break;
}
lua_pop(luaContext->lua, 2);
value = lua_touserdata(luaContext->lua, -1);
value = mScriptContextAccessWeakref(luaContext->d.context, value);
break;
}
lua_pop(luaContext->lua, 1);
return value;
}
bool _luaWrap(struct mScriptEngineContextLua* luaContext, struct mScriptValue* value) {
if (!value) {
lua_pushnil(luaContext->lua);
return true;
}
uint32_t weakref;
bool needsWeakref = false;
if (value->type->base == mSCRIPT_TYPE_WRAPPER) {
value = mScriptValueUnwrap(value);
if (!value) {
lua_pushnil(luaContext->lua);
return true;
}
}
if (value->type == mSCRIPT_TYPE_MS_WEAKREF) {
weakref = value->value.u32;
value = mScriptContextAccessWeakref(luaContext->d.context, value);
if (!value) {
lua_pushnil(luaContext->lua);
return true;
}
needsWeakref = true;
}
bool ok = true;
struct mScriptValue* newValue;
switch (value->type->base) {
case mSCRIPT_TYPE_SINT:
if (value->type->size <= 4) {
lua_pushinteger(luaContext->lua, value->value.s32);
} else if (value->type->size == 8) {
lua_pushinteger(luaContext->lua, value->value.s64);
} else {
ok = false;
}
break;
case mSCRIPT_TYPE_UINT:
if (value->type->size <= 4) {
lua_pushinteger(luaContext->lua, value->value.u32);
} else if (value->type->size == 8) {
lua_pushinteger(luaContext->lua, value->value.u64);
} else {
ok = false;
}
break;
case mSCRIPT_TYPE_FLOAT:
if (value->type->size == 4) {
lua_pushnumber(luaContext->lua, value->value.f32);
} else if (value->type->size == 8) {
lua_pushnumber(luaContext->lua, value->value.f64);
} else {
ok = false;
}
break;
case mSCRIPT_TYPE_STRING:
lua_pushlstring(luaContext->lua, value->value.string->buffer, value->value.string->size);
break;
case mSCRIPT_TYPE_LIST:
newValue = lua_newuserdata(luaContext->lua, sizeof(*newValue));
if (needsWeakref) {
*newValue = mSCRIPT_MAKE(WEAKREF, weakref);
} else {
mScriptValueWrap(value, newValue);
mScriptValueDeref(value);
}
luaL_setmetatable(luaContext->lua, "mSTList");
break;
case mSCRIPT_TYPE_TABLE:
newValue = lua_newuserdata(luaContext->lua, sizeof(*newValue));
if (needsWeakref) {
*newValue = mSCRIPT_MAKE(WEAKREF, weakref);
} else {
mScriptValueWrap(value, newValue);
mScriptValueDeref(value);
}
luaL_setmetatable(luaContext->lua, "mSTTable");
break;
case mSCRIPT_TYPE_FUNCTION:
newValue = lua_newuserdata(luaContext->lua, sizeof(*newValue));
newValue->type = value->type;
newValue->refs = mSCRIPT_VALUE_UNREF;
newValue->type->alloc(newValue);
lua_pushcclosure(luaContext->lua, _luaThunk, 1);
mScriptValueDeref(value);
break;
case mSCRIPT_TYPE_OBJECT:
newValue = lua_newuserdata(luaContext->lua, sizeof(*newValue));
if (needsWeakref) {
*newValue = mSCRIPT_MAKE(WEAKREF, weakref);
} else {
mScriptValueWrap(value, newValue);
mScriptValueDeref(value);
}
luaL_setmetatable(luaContext->lua, "mSTStruct");
break;
default:
ok = false;
break;
}
return ok;
}
#define LUA_BLOCKSIZE 0x1000
struct mScriptEngineLuaReader {
struct VFile* vf;
char block[LUA_BLOCKSIZE];
};
static const char* _reader(lua_State* lua, void* context, size_t* size) {
UNUSED(lua);
struct mScriptEngineLuaReader* reader = context;
ssize_t s = reader->vf->read(reader->vf, reader->block, sizeof(reader->block));
if (s < 0) {
return NULL;
}
*size = s;
return reader->block;
}
bool _luaLoad(struct mScriptEngineContext* ctx, const char* filename, struct VFile* vf) {
struct mScriptEngineContextLua* luaContext = (struct mScriptEngineContextLua*) ctx;
struct mScriptEngineLuaReader data = {
.vf = vf
};
if (luaContext->lastError) {
free(luaContext->lastError);
luaContext->lastError = NULL;
}
char name[80];
if (filename) {
if (*filename == '*') {
snprintf(name, sizeof(name), "=%s", filename + 1);
} else {
const char* lastSlash = strrchr(filename, '/');
const char* lastBackslash = strrchr(filename, '\\');
if (lastSlash && lastBackslash) {
if (lastSlash > lastBackslash) {
filename = lastSlash + 1;
} else {
filename = lastBackslash + 1;
}
} else if (lastSlash) {
filename = lastSlash + 1;
} else if (lastBackslash) {
filename = lastBackslash + 1;
}
snprintf(name, sizeof(name), "@%s", filename);
}
filename = name;
}
int ret = lua_load(luaContext->lua, _reader, &data, filename, "t");
switch (ret) {
case LUA_OK:
luaContext->func = luaL_ref(luaContext->lua, LUA_REGISTRYINDEX);
return true;
case LUA_ERRSYNTAX:
luaContext->lastError = strdup(lua_tostring(luaContext->lua, -1));
lua_pop(luaContext->lua, 1);
break;
default:
break;
}
return false;
}
bool _luaRun(struct mScriptEngineContext* context) {
struct mScriptEngineContextLua* luaContext = (struct mScriptEngineContextLua*) context;
lua_rawgeti(luaContext->lua, LUA_REGISTRYINDEX, luaContext->func);
return _luaInvoke(luaContext, NULL);
}
const char* _luaGetError(struct mScriptEngineContext* context) {
struct mScriptEngineContextLua* luaContext = (struct mScriptEngineContextLua*) context;
return luaContext->lastError;
}
bool _luaPushFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList* frame, bool internal) {
bool ok = true;
if (frame) {
size_t i;
for (i = 0; i < mScriptListSize(frame); ++i) {
struct mScriptValue* value = mScriptListGetPointer(frame, i);
if (internal && value->type->base == mSCRIPT_TYPE_WRAPPER) {
value = mScriptValueUnwrap(value);
mScriptContextFillPool(luaContext->d.context, value);
}
if (!_luaWrap(luaContext, value)) {
ok = false;
break;
}
}
}
if (!ok) {
lua_pop(luaContext->lua, lua_gettop(luaContext->lua));
}
return ok;
}
bool _luaPopFrame(struct mScriptEngineContextLua* luaContext, struct mScriptList* frame) {
int count = lua_gettop(luaContext->lua);
bool ok = true;
if (frame) {
int i;
for (i = 0; i < count; ++i) {
struct mScriptValue* value = _luaCoerce(luaContext);
if (!value) {
ok = false;
break;
}
mScriptValueWrap(value, mScriptListAppend(frame));
mScriptValueDeref(value);
}
if (count > i) {
lua_pop(luaContext->lua, count - i);
}
if (ok) {
for (i = 0; i < (ssize_t) (mScriptListSize(frame) / 2); ++i) {
struct mScriptValue buffer;
memcpy(&buffer, mScriptListGetPointer(frame, i), sizeof(buffer));
memcpy(mScriptListGetPointer(frame, i), mScriptListGetPointer(frame, mScriptListSize(frame) - i - 1), sizeof(buffer));
memcpy(mScriptListGetPointer(frame, mScriptListSize(frame) - i - 1), &buffer, sizeof(buffer));
}
}
}
return ok;
}
bool _luaCall(struct mScriptFrame* frame, void* context) {
struct mScriptEngineContextLuaRef* ref = context;
lua_rawgeti(ref->context->lua, LUA_REGISTRYINDEX, ref->ref);
if (!_luaInvoke(ref->context, frame)) {
return false;
}
return true;
}
bool _luaInvoke(struct mScriptEngineContextLua* luaContext, struct mScriptFrame* frame) {
int nargs = 0;
if (frame) {
nargs = mScriptListSize(&frame->arguments);
}
if (luaContext->lastError) {
free(luaContext->lastError);
luaContext->lastError = NULL;
}
if (frame && !_luaPushFrame(luaContext, &frame->arguments, false)) {
return false;
}
lua_pushliteral(luaContext->lua, "mCtx");
lua_pushlightuserdata(luaContext->lua, luaContext);
lua_rawset(luaContext->lua, LUA_REGISTRYINDEX);
int ret = lua_pcall(luaContext->lua, nargs, LUA_MULTRET, 0);
lua_pushliteral(luaContext->lua, "mCtx");
lua_pushnil(luaContext->lua);
lua_rawset(luaContext->lua, LUA_REGISTRYINDEX);
if (ret == LUA_ERRRUN) {
luaContext->lastError = strdup(lua_tostring(luaContext->lua, -1));
lua_pop(luaContext->lua, 1);
}
if (ret) {
return false;
}
if (frame && !_luaPopFrame(luaContext, &frame->returnValues)) {
mScriptContextDrainPool(luaContext->d.context);
return false;
}
mScriptContextDrainPool(luaContext->d.context);
return true;
}
void _luaDeref(struct mScriptValue* value) {
struct mScriptEngineContextLuaRef* ref;
if (value->type->base == mSCRIPT_TYPE_FUNCTION) {
struct mScriptFunction* function = value->value.opaque;
ref = function->context;
free(function);
} else {
return;
}
luaL_unref(ref->context->lua, LUA_REGISTRYINDEX, ref->ref);
free(ref);
}
static struct mScriptEngineContextLua* _luaGetContext(lua_State* lua) {
lua_pushliteral(lua, "mCtx");
int type = lua_rawget(lua, LUA_REGISTRYINDEX);
if (type != LUA_TLIGHTUSERDATA) {
lua_pop(lua, 1);
lua_pushliteral(lua, "Function called from invalid context");
lua_error(lua);
}
struct mScriptEngineContextLua* luaContext = lua_touserdata(lua, -1);
lua_pop(lua, 1);
if (luaContext->lua != lua) {
lua_pushliteral(lua, "Function called from invalid context");
lua_error(lua);
}
return luaContext;
}
int _luaThunk(lua_State* lua) {
struct mScriptEngineContextLua* luaContext = _luaGetContext(lua);
struct mScriptFrame frame;
mScriptFrameInit(&frame);
if (!_luaPopFrame(luaContext, &frame.arguments)) {
mScriptContextDrainPool(luaContext->d.context);
mScriptFrameDeinit(&frame);
luaL_traceback(lua, lua, "Error calling function (translating arguments into runtime)", 1);
lua_error(lua);
}
struct mScriptValue* fn = lua_touserdata(lua, lua_upvalueindex(1));
if (!fn || !mScriptInvoke(fn, &frame)) {
mScriptFrameDeinit(&frame);
luaL_traceback(lua, lua, "Error calling function (invoking failed)", 1);
lua_error(lua);
}
if (!_luaPushFrame(luaContext, &frame.returnValues, true)) {
mScriptFrameDeinit(&frame);
luaL_traceback(lua, lua, "Error calling function (translating return values from runtime)", 1);
lua_error(lua);
}
mScriptContextDrainPool(luaContext->d.context);
mScriptFrameDeinit(&frame);
return lua_gettop(luaContext->lua);
}
int _luaGetObject(lua_State* lua) {
struct mScriptEngineContextLua* luaContext = _luaGetContext(lua);
char key[MAX_KEY_SIZE];
const char* keyPtr = lua_tostring(lua, -1);
struct mScriptValue* obj = lua_touserdata(lua, -2);
struct mScriptValue val;
if (!keyPtr) {
lua_pop(lua, 2);
luaL_traceback(lua, lua, "Invalid key", 1);
lua_error(lua);
}
strlcpy(key, keyPtr, sizeof(key));
lua_pop(lua, 2);
obj = mScriptContextAccessWeakref(luaContext->d.context, obj);
if (!obj) {
luaL_traceback(lua, lua, "Invalid object", 1);
lua_error(lua);
}
if (!mScriptObjectGet(obj, key, &val)) {
char error[MAX_KEY_SIZE + 16];
snprintf(error, sizeof(error), "Invalid key '%s'", key);
luaL_traceback(lua, lua, "Invalid key", 1);
lua_error(lua);
}
if (!_luaWrap(luaContext, &val)) {
luaL_traceback(lua, lua, "Error translating value from runtime", 1);
lua_error(lua);
}
return 1;
}
int _luaSetObject(lua_State* lua) {
struct mScriptEngineContextLua* luaContext = _luaGetContext(lua);
char key[MAX_KEY_SIZE];
const char* keyPtr = lua_tostring(lua, -2);
struct mScriptValue* obj = lua_touserdata(lua, -3);
struct mScriptValue* val = _luaCoerce(luaContext);
if (!keyPtr) {
lua_pop(lua, 2);
luaL_traceback(lua, lua, "Invalid key", 1);
lua_error(lua);
}
strlcpy(key, keyPtr, sizeof(key));
lua_pop(lua, 2);
obj = mScriptContextAccessWeakref(luaContext->d.context, obj);
if (!obj) {
luaL_traceback(lua, lua, "Invalid object", 1);
lua_error(lua);
}
if (!val) {
luaL_traceback(lua, lua, "Error translating value to runtime", 1);
lua_error(lua);
}
if (!mScriptObjectSet(obj, key, val)) {
mScriptValueDeref(val);
char error[MAX_KEY_SIZE + 16];
snprintf(error, sizeof(error), "Invalid key '%s'", key);
luaL_traceback(lua, lua, "Invalid key", 1);
lua_error(lua);
}
mScriptValueDeref(val);
mScriptContextDrainPool(luaContext->d.context);
return 0;
}
static int _luaGcObject(lua_State* lua) {
struct mScriptValue* val = lua_touserdata(lua, -1);
val = mScriptValueUnwrap(val);
if (!val) {
return 0;
}
mScriptValueDeref(val);
return 0;
}
int _luaGetTable(lua_State* lua) {
struct mScriptEngineContextLua* luaContext = _luaGetContext(lua);
char key[MAX_KEY_SIZE];
int type = lua_type(luaContext->lua, -1);
const char* keyPtr = NULL;
int64_t intKey;
switch (type) {
case LUA_TNUMBER:
intKey = lua_tointeger(luaContext->lua, -1);
break;
case LUA_TSTRING:
keyPtr = lua_tostring(lua, -1);
break;
default:
lua_pop(lua, 2);
return 0;
}
struct mScriptValue* obj = lua_touserdata(lua, -2);
if (keyPtr) {
strlcpy(key, keyPtr, sizeof(key));
}
lua_pop(lua, 2);
obj = mScriptContextAccessWeakref(luaContext->d.context, obj);
if (!obj) {
luaL_traceback(lua, lua, "Invalid table", 1);
lua_error(lua);
}
struct mScriptValue keyVal;
switch (type) {
case LUA_TNUMBER:
keyVal = mSCRIPT_MAKE_S64(intKey);
break;
case LUA_TSTRING:
keyVal = mSCRIPT_MAKE_CHARP(key);
break;
}
struct mScriptValue* val = mScriptTableLookup(obj, &keyVal);
if (!val) {
return 0;
}
if (!_luaWrap(luaContext, val)) {
luaL_traceback(lua, lua, "Error translating value from runtime", 1);
lua_error(lua);
}
return 1;
}
int _luaLenTable(lua_State* lua) {
struct mScriptEngineContextLua* luaContext = _luaGetContext(lua);
struct mScriptValue* obj = lua_touserdata(lua, -1);
lua_pop(lua, 1);
obj = mScriptContextAccessWeakref(luaContext->d.context, obj);
if (!obj) {
luaL_traceback(lua, lua, "Invalid table", 1);
lua_error(lua);
}
struct mScriptValue val = mSCRIPT_MAKE_U64(mScriptTableSize(obj));
if (!_luaWrap(luaContext, &val)) {
luaL_traceback(lua, lua, "Error translating value from runtime", 1);
lua_error(lua);
}
return 1;
}
static int _luaNextTable(lua_State* lua) {
struct mScriptEngineContextLua* luaContext = _luaGetContext(lua);
char key[MAX_KEY_SIZE];
int type = lua_type(luaContext->lua, -1);
const char* keyPtr = NULL;
struct mScriptValue keyVal = {0};
switch (type) {
case LUA_TNUMBER:
keyVal = mSCRIPT_MAKE_S64(lua_tointeger(luaContext->lua, -1));
break;
case LUA_TSTRING:
keyPtr = lua_tostring(lua, -1);
break;
}
struct mScriptValue* table = lua_touserdata(lua, -2);
if (keyPtr) {
strlcpy(key, keyPtr, sizeof(key));
keyVal = mSCRIPT_MAKE_CHARP(key);
}
lua_pop(lua, 2);
table = mScriptContextAccessWeakref(luaContext->d.context, table);
if (!table) {
luaL_traceback(lua, lua, "Invalid table", 1);
lua_error(lua);
}
struct TableIterator iter;
if (keyVal.type) {
if (!mScriptTableIteratorLookup(table, &iter, &keyVal)) {
return 0;
}
if (!mScriptTableIteratorNext(table, &iter)) {
return 0;
}
} else {
if (!mScriptTableIteratorStart(table, &iter)) {
return 0;
}
}
if (!_luaWrap(luaContext, mScriptTableIteratorGetKey(table, &iter))) {
luaL_traceback(lua, lua, "Iteration error", 1);
lua_error(lua);
}
if (!_luaWrap(luaContext, mScriptTableIteratorGetValue(table, &iter))) {
luaL_traceback(lua, lua, "Iteration error", 1);
lua_error(lua);
}
return 2;
}
int _luaPairsTable(lua_State* lua) {
lua_pushcfunction(lua, _luaNextTable);
lua_insert(lua, -2);
lua_pushnil(lua);
return 3;
}
int _luaGetList(lua_State* lua) {
struct mScriptEngineContextLua* luaContext = _luaGetContext(lua);
ssize_t index;
#if LUA_VERSION_NUM >= 503
index = lua_tointeger(luaContext->lua, -1);
#else
index = lua_tonumber(luaContext->lua, -1);
#endif
struct mScriptValue* obj = lua_touserdata(lua, -2);
lua_pop(lua, 2);
obj = mScriptContextAccessWeakref(luaContext->d.context, obj);
if (obj->type->base == mSCRIPT_TYPE_WRAPPER) {
obj = mScriptValueUnwrap(obj);
}
if (!obj || obj->type != mSCRIPT_TYPE_MS_LIST) {
luaL_traceback(lua, lua, "Invalid list", 1);
lua_error(lua);
}
struct mScriptList* list = obj->value.list;
// Lua indexes from 1
if (index < 1) {
luaL_traceback(lua, lua, "Invalid index", 1);
lua_error(lua);
}
if ((size_t) index > mScriptListSize(list)) {
return 0;
}
--index;
struct mScriptValue* val = mScriptListGetPointer(list, index);
if (!_luaWrap(luaContext, val)) {
luaL_traceback(lua, lua, "Error translating value from runtime", 1);
lua_error(lua);
}
return 1;
}
static int _luaLenList(lua_State* lua) {
struct mScriptEngineContextLua* luaContext = _luaGetContext(lua);
struct mScriptValue* obj = lua_touserdata(lua, -1);
lua_pop(lua, 1);
obj = mScriptContextAccessWeakref(luaContext->d.context, obj);
if (obj->type->base == mSCRIPT_TYPE_WRAPPER) {
obj = mScriptValueUnwrap(obj);
}
if (!obj || obj->type != mSCRIPT_TYPE_MS_LIST) {
luaL_traceback(lua, lua, "Invalid list", 1);
lua_error(lua);
}
struct mScriptList* list = obj->value.list;
lua_pushinteger(lua, mScriptListSize(list));
return 1;
}

110
src/script/stdlib.c Normal file
View File

@ -0,0 +1,110 @@
/* Copyright (c) 2013-2022 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/context.h>
#include <mgba/core/core.h>
#include <mgba/core/serialize.h>
#include <mgba/script/macros.h>
#ifdef M_CORE_GBA
#include <mgba/internal/gba/input.h>
#endif
#ifdef M_CORE_GB
#include <mgba/internal/gb/input.h>
#endif
struct mScriptCallbackManager {
struct mScriptContext* context;
};
static void _mScriptCallbackAdd(struct mScriptCallbackManager* adapter, struct mScriptString* name, struct mScriptValue* fn) {
if (fn->type->base == mSCRIPT_TYPE_WRAPPER) {
fn = mScriptValueUnwrap(fn);
}
mScriptContextAddCallback(adapter->context, name->buffer, fn);
mScriptValueDeref(fn);
}
mSCRIPT_DECLARE_STRUCT(mScriptCallbackManager);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptCallbackManager, add, _mScriptCallbackAdd, 2, STR, callback, WRAPPER, function);
mSCRIPT_DEFINE_STRUCT(mScriptCallbackManager)
mSCRIPT_DEFINE_CLASS_DOCSTRING(
"A global singleton object `callbacks` used for managing callbacks. The following callbacks are defined:\n\n"
"- **alarm**: An in-game alarm went off\n"
"- **crashed**: The emulation crashed\n"
"- **frame**: The emulation finished a frame\n"
"- **keysRead**: The emulation is about to read the key input\n"
"- **reset**: The emulation has been reset\n"
"- **savedataUpdated**: The emulation has just finished modifying save data\n"
"- **sleep**: The emulation has used the sleep feature to enter a low-power mode\n"
"- **shutdown**: The emulation has been powered off\n"
"- **start**: The emulation has started\n"
"- **stop**: The emulation has voluntarily shut down\n"
)
mSCRIPT_DEFINE_DOCSTRING("Add a callback of the named type")
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptCallbackManager, add)
mSCRIPT_DEFINE_END;
void mScriptContextAttachStdlib(struct mScriptContext* context) {
struct mScriptValue* lib;
lib = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptCallbackManager));
lib->value.opaque = calloc(1, sizeof(struct mScriptCallbackManager));
*(struct mScriptCallbackManager*) lib->value.opaque = (struct mScriptCallbackManager) {
.context = context
};
lib->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
mScriptContextSetGlobal(context, "callbacks", lib);
mScriptContextExportConstants(context, "SAVESTATE", (struct mScriptKVPair[]) {
mSCRIPT_CONSTANT_PAIR(SAVESTATE, SCREENSHOT),
mSCRIPT_CONSTANT_PAIR(SAVESTATE, SAVEDATA),
mSCRIPT_CONSTANT_PAIR(SAVESTATE, CHEATS),
mSCRIPT_CONSTANT_PAIR(SAVESTATE, RTC),
mSCRIPT_CONSTANT_PAIR(SAVESTATE, METADATA),
mSCRIPT_CONSTANT_PAIR(SAVESTATE, ALL),
mSCRIPT_CONSTANT_SENTINEL
});
mScriptContextExportConstants(context, "PLATFORM", (struct mScriptKVPair[]) {
mSCRIPT_CONSTANT_PAIR(mPLATFORM, NONE),
mSCRIPT_CONSTANT_PAIR(mPLATFORM, GBA),
mSCRIPT_CONSTANT_PAIR(mPLATFORM, GB),
mSCRIPT_CONSTANT_SENTINEL
});
mScriptContextExportConstants(context, "CHECKSUM", (struct mScriptKVPair[]) {
mSCRIPT_CONSTANT_PAIR(mCHECKSUM, CRC32),
mSCRIPT_CONSTANT_SENTINEL
});
#ifdef M_CORE_GBA
mScriptContextExportConstants(context, "GBA_KEY", (struct mScriptKVPair[]) {
mSCRIPT_CONSTANT_PAIR(GBA_KEY, A),
mSCRIPT_CONSTANT_PAIR(GBA_KEY, B),
mSCRIPT_CONSTANT_PAIR(GBA_KEY, SELECT),
mSCRIPT_CONSTANT_PAIR(GBA_KEY, START),
mSCRIPT_CONSTANT_PAIR(GBA_KEY, RIGHT),
mSCRIPT_CONSTANT_PAIR(GBA_KEY, LEFT),
mSCRIPT_CONSTANT_PAIR(GBA_KEY, UP),
mSCRIPT_CONSTANT_PAIR(GBA_KEY, DOWN),
mSCRIPT_CONSTANT_PAIR(GBA_KEY, R),
mSCRIPT_CONSTANT_PAIR(GBA_KEY, L),
mSCRIPT_CONSTANT_SENTINEL
});
#endif
#ifdef M_CORE_GB
mScriptContextExportConstants(context, "GB_KEY", (struct mScriptKVPair[]) {
mSCRIPT_CONSTANT_PAIR(GB_KEY, A),
mSCRIPT_CONSTANT_PAIR(GB_KEY, B),
mSCRIPT_CONSTANT_PAIR(GB_KEY, SELECT),
mSCRIPT_CONSTANT_PAIR(GB_KEY, START),
mSCRIPT_CONSTANT_PAIR(GB_KEY, RIGHT),
mSCRIPT_CONSTANT_PAIR(GB_KEY, LEFT),
mSCRIPT_CONSTANT_PAIR(GB_KEY, UP),
mSCRIPT_CONSTANT_PAIR(GB_KEY, DOWN),
mSCRIPT_CONSTANT_SENTINEL
});
#endif
mScriptContextSetGlobal(context, "C", context->constants);
}

985
src/script/test/classes.c Normal file
View File

@ -0,0 +1,985 @@
/* Copyright (c) 2013-2022 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/context.h>
#include <mgba/script/macros.h>
#include <mgba/script/types.h>
struct TestA {
int32_t i;
int32_t i2;
int8_t b8;
int16_t hUnaligned;
int32_t (*ifn0)(struct TestA*);
int32_t (*ifn1)(struct TestA*, int);
void (*vfn0)(struct TestA*);
void (*vfn1)(struct TestA*, int);
int32_t (*icfn0)(const struct TestA*);
int32_t (*icfn1)(const struct TestA*, int);
};
struct TestB {
struct TestA d;
int32_t i3;
};
struct TestC {
int32_t i;
};
struct TestD {
struct TestC a;
struct TestC b;
};
struct TestE {
};
struct TestF {
int* ref;
};
static int32_t testAi0(struct TestA* a) {
return a->i;
}
static int32_t testAi1(struct TestA* a, int b) {
return a->i + b;
}
static int32_t testAic0(const struct TestA* a) {
return a->i;
}
static int32_t testAic1(const struct TestA* a, int b) {
return a->i + b;
}
static void testAv0(struct TestA* a) {
++a->i;
}
static void testAv1(struct TestA* a, int b) {
a->i += b;
}
static void testAv2(struct TestA* a, int b, int c) {
a->i += b + c;
}
static int32_t testGet(struct TestE* e, const char* name) {
UNUSED(e);
return name[0];
}
static void testDeinit(struct TestF* f) {
++*f->ref;
}
#define MEMBER_A_DOCSTRING "Member a"
mSCRIPT_DECLARE_STRUCT(TestA);
mSCRIPT_DECLARE_STRUCT_D_METHOD(TestA, S32, ifn0, 0);
mSCRIPT_DECLARE_STRUCT_D_METHOD(TestA, S32, ifn1, 1, S32, b);
mSCRIPT_DECLARE_STRUCT_CD_METHOD(TestA, S32, icfn0, 0);
mSCRIPT_DECLARE_STRUCT_CD_METHOD(TestA, S32, icfn1, 1, S32, b);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(TestA, vfn0, 0);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(TestA, vfn1, 1, S32, b);
mSCRIPT_DECLARE_STRUCT_METHOD(TestA, S32, i0, testAi0, 0);
mSCRIPT_DECLARE_STRUCT_METHOD(TestA, S32, i1, testAi1, 1, S32, b);
mSCRIPT_DECLARE_STRUCT_C_METHOD(TestA, S32, ic0, testAic0, 0);
mSCRIPT_DECLARE_STRUCT_C_METHOD(TestA, S32, ic1, testAic1, 1, S32, b);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestA, v0, testAv0, 0);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestA, v1, testAv1, 1, S32, b);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD_WITH_DEFAULTS(TestA, v2, testAv2, 2, S32, b, S32, c);
mSCRIPT_DEFINE_STRUCT(TestA)
mSCRIPT_DEFINE_DOCSTRING(MEMBER_A_DOCSTRING)
mSCRIPT_DEFINE_STRUCT_MEMBER(TestA, S32, i)
mSCRIPT_DEFINE_STRUCT_MEMBER(TestA, S32, i2)
mSCRIPT_DEFINE_STRUCT_MEMBER(TestA, S8, b8)
mSCRIPT_DEFINE_STRUCT_MEMBER(TestA, S16, hUnaligned)
mSCRIPT_DEFINE_STRUCT_METHOD(TestA, ifn0)
mSCRIPT_DEFINE_STRUCT_METHOD(TestA, ifn1)
mSCRIPT_DEFINE_STRUCT_METHOD(TestA, icfn0)
mSCRIPT_DEFINE_STRUCT_METHOD(TestA, icfn1)
mSCRIPT_DEFINE_STRUCT_METHOD(TestA, vfn0)
mSCRIPT_DEFINE_STRUCT_METHOD(TestA, vfn1)
mSCRIPT_DEFINE_STRUCT_METHOD(TestA, i0)
mSCRIPT_DEFINE_STRUCT_METHOD(TestA, i1)
mSCRIPT_DEFINE_STRUCT_METHOD(TestA, ic0)
mSCRIPT_DEFINE_STRUCT_METHOD(TestA, ic1)
mSCRIPT_DEFINE_STRUCT_METHOD(TestA, v0)
mSCRIPT_DEFINE_STRUCT_METHOD(TestA, v1)
mSCRIPT_DEFINE_STRUCT_METHOD(TestA, v2)
mSCRIPT_DEFINE_END;
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(TestA, v2)
mSCRIPT_NO_DEFAULT,
mSCRIPT_MAKE_S32(0)
mSCRIPT_DEFINE_DEFAULTS_END;
mSCRIPT_DEFINE_STRUCT(TestB)
mSCRIPT_DEFINE_INHERIT(TestA)
mSCRIPT_DEFINE_STRUCT_MEMBER(TestB, S32, i3)
mSCRIPT_DEFINE_END;
mSCRIPT_DEFINE_STRUCT(TestC)
mSCRIPT_DEFINE_STRUCT_MEMBER(TestC, S32, i)
mSCRIPT_DEFINE_END;
mSCRIPT_DEFINE_STRUCT(TestD)
mSCRIPT_DEFINE_STRUCT_MEMBER(TestD, S(TestC), a)
mSCRIPT_DEFINE_STRUCT_MEMBER(TestD, S(TestC), b)
mSCRIPT_DEFINE_END;
mSCRIPT_DECLARE_STRUCT(TestE);
mSCRIPT_DECLARE_STRUCT_METHOD(TestE, S32, _get, testGet, 1, CHARP, name);
mSCRIPT_DEFINE_STRUCT(TestE)
mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(TestE)
mSCRIPT_DEFINE_END;
mSCRIPT_DECLARE_STRUCT(TestF);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(TestF, _deinit, testDeinit, 0);
mSCRIPT_DEFINE_STRUCT(TestF)
mSCRIPT_DEFINE_STRUCT_DEINIT(TestF)
mSCRIPT_DEFINE_END;
M_TEST_DEFINE(testALayout) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls;
assert_false(cls->init);
mScriptClassInit(cls);
assert_true(cls->init);
struct mScriptClassMember* member;
member = HashTableLookup(&cls->instanceMembers, "i");
assert_non_null(member);
assert_string_equal(member->name, "i");
assert_string_equal(member->docstring, MEMBER_A_DOCSTRING);
assert_ptr_equal(member->type, mSCRIPT_TYPE_MS_S32);
assert_int_equal(member->offset, 0);
member = HashTableLookup(&cls->instanceMembers, "i2");
assert_non_null(member);
assert_string_equal(member->name, "i2");
assert_null(member->docstring);
assert_ptr_equal(member->type, mSCRIPT_TYPE_MS_S32);
assert_int_equal(member->offset, sizeof(int32_t));
member = HashTableLookup(&cls->instanceMembers, "b8");
assert_non_null(member);
assert_string_equal(member->name, "b8");
assert_null(member->docstring);
assert_ptr_equal(member->type, mSCRIPT_TYPE_MS_S8);
assert_int_equal(member->offset, sizeof(int32_t) * 2);
member = HashTableLookup(&cls->instanceMembers, "hUnaligned");
assert_non_null(member);
assert_string_equal(member->name, "hUnaligned");
assert_null(member->docstring);
assert_ptr_equal(member->type, mSCRIPT_TYPE_MS_S16);
assert_int_not_equal(member->offset, sizeof(int32_t) * 2 + 1);
member = HashTableLookup(&cls->instanceMembers, "unknown");
assert_null(member);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_DEFINE(testASignatures) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls;
assert_false(cls->init);
mScriptClassInit(cls);
assert_true(cls->init);
struct mScriptClassMember* member;
member = HashTableLookup(&cls->instanceMembers, "ifn0");
assert_non_null(member);
assert_string_equal(member->name, "ifn0");
assert_int_equal(member->type->base, mSCRIPT_TYPE_FUNCTION);
assert_int_equal(member->type->details.function.parameters.count, 1);
assert_ptr_equal(member->type->details.function.parameters.entries[0], mSCRIPT_TYPE_MS_S(TestA));
assert_string_equal(member->type->details.function.parameters.names[0], "this");
assert_int_equal(member->type->details.function.returnType.count, 1);
assert_ptr_equal(member->type->details.function.returnType.entries[0], mSCRIPT_TYPE_MS_S32);
member = HashTableLookup(&cls->instanceMembers, "ifn1");
assert_non_null(member);
assert_string_equal(member->name, "ifn1");
assert_int_equal(member->type->base, mSCRIPT_TYPE_FUNCTION);
assert_int_equal(member->type->details.function.parameters.count, 2);
assert_ptr_equal(member->type->details.function.parameters.entries[0], mSCRIPT_TYPE_MS_S(TestA));
assert_ptr_equal(member->type->details.function.parameters.entries[1], mSCRIPT_TYPE_MS_S32);
assert_string_equal(member->type->details.function.parameters.names[0], "this");
assert_string_equal(member->type->details.function.parameters.names[1], "b");
assert_int_equal(member->type->details.function.returnType.count, 1);
assert_ptr_equal(member->type->details.function.returnType.entries[0], mSCRIPT_TYPE_MS_S32);
member = HashTableLookup(&cls->instanceMembers, "vfn0");
assert_non_null(member);
assert_string_equal(member->name, "vfn0");
assert_int_equal(member->type->base, mSCRIPT_TYPE_FUNCTION);
assert_int_equal(member->type->details.function.parameters.count, 1);
assert_ptr_equal(member->type->details.function.parameters.entries[0], mSCRIPT_TYPE_MS_S(TestA));
assert_string_equal(member->type->details.function.parameters.names[0], "this");
assert_int_equal(member->type->details.function.returnType.count, 0);
member = HashTableLookup(&cls->instanceMembers, "vfn1");
assert_non_null(member);
assert_string_equal(member->name, "vfn1");
assert_int_equal(member->type->base, mSCRIPT_TYPE_FUNCTION);
assert_int_equal(member->type->details.function.parameters.count, 2);
assert_ptr_equal(member->type->details.function.parameters.entries[0], mSCRIPT_TYPE_MS_S(TestA));
assert_ptr_equal(member->type->details.function.parameters.entries[1], mSCRIPT_TYPE_MS_S32);
assert_string_equal(member->type->details.function.parameters.names[0], "this");
assert_string_equal(member->type->details.function.parameters.names[1], "b");
assert_int_equal(member->type->details.function.returnType.count, 0);
member = HashTableLookup(&cls->instanceMembers, "icfn0");
assert_non_null(member);
assert_string_equal(member->name, "icfn0");
assert_int_equal(member->type->base, mSCRIPT_TYPE_FUNCTION);
assert_int_equal(member->type->details.function.parameters.count, 1);
assert_ptr_equal(member->type->details.function.parameters.entries[0], mSCRIPT_TYPE_MS_CS(TestA));
assert_string_equal(member->type->details.function.parameters.names[0], "this");
assert_int_equal(member->type->details.function.returnType.count, 1);
assert_ptr_equal(member->type->details.function.returnType.entries[0], mSCRIPT_TYPE_MS_S32);
member = HashTableLookup(&cls->instanceMembers, "icfn1");
assert_non_null(member);
assert_string_equal(member->name, "icfn1");
assert_int_equal(member->type->base, mSCRIPT_TYPE_FUNCTION);
assert_int_equal(member->type->details.function.parameters.count, 2);
assert_ptr_equal(member->type->details.function.parameters.entries[0], mSCRIPT_TYPE_MS_CS(TestA));
assert_ptr_equal(member->type->details.function.parameters.entries[1], mSCRIPT_TYPE_MS_S32);
assert_string_equal(member->type->details.function.parameters.names[0], "this");
assert_string_equal(member->type->details.function.parameters.names[1], "b");
assert_int_equal(member->type->details.function.returnType.count, 1);
assert_ptr_equal(member->type->details.function.returnType.entries[0], mSCRIPT_TYPE_MS_S32);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_DEFINE(testAGet) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls;
struct TestA s = {
.i = 1,
.i2 = 2,
.b8 = 3,
.hUnaligned = 4
};
struct mScriptValue sval = mSCRIPT_MAKE_S(TestA, &s);
struct mScriptValue val;
struct mScriptValue compare;
compare = mSCRIPT_MAKE_S32(1);
assert_true(mScriptObjectGet(&sval, "i", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32(2);
assert_true(mScriptObjectGet(&sval, "i2", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32(3);
assert_true(mScriptObjectGet(&sval, "b8", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32(4);
assert_true(mScriptObjectGet(&sval, "hUnaligned", &val));
assert_true(compare.type->equal(&compare, &val));
assert_false(mScriptObjectGet(&sval, "unknown", &val));
assert_true(cls->init);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_DEFINE(testASet) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls;
struct TestA s = {
.i = 1,
.i2 = 2,
.b8 = 3,
.hUnaligned = 4
};
struct mScriptValue sval = mSCRIPT_MAKE_S(TestA, &s);
struct mScriptValue val;
val = mSCRIPT_MAKE_S32(2);
assert_true(mScriptObjectSet(&sval, "i", &val));
assert_int_equal(s.i, 2);
val = mSCRIPT_MAKE_S32(3);
assert_true(mScriptObjectSet(&sval, "i2", &val));
assert_int_equal(s.i2, 3);
val = mSCRIPT_MAKE_S32(4);
assert_true(mScriptObjectSet(&sval, "b8", &val));
assert_int_equal(s.b8, 4);
val = mSCRIPT_MAKE_S32(5);
assert_true(mScriptObjectSet(&sval, "hUnaligned", &val));
assert_int_equal(s.hUnaligned, 5);
sval = mSCRIPT_MAKE_CS(TestA, &s);
val = mSCRIPT_MAKE_S32(3);
assert_false(mScriptObjectSet(&sval, "i", &val));
assert_int_equal(s.i, 2);
val = mSCRIPT_MAKE_S32(4);
assert_false(mScriptObjectSet(&sval, "i2", &val));
assert_int_equal(s.i2, 3);
val = mSCRIPT_MAKE_S32(5);
assert_false(mScriptObjectSet(&sval, "b8", &val));
assert_int_equal(s.b8, 4);
val = mSCRIPT_MAKE_S32(6);
assert_false(mScriptObjectSet(&sval, "hUnaligned", &val));
assert_int_equal(s.hUnaligned, 5);
assert_false(mScriptObjectSet(&sval, "unknown", &val));
assert_true(cls->init);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_DEFINE(testAStatic) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls;
assert_false(cls->init);
mScriptClassInit(cls);
assert_true(cls->init);
struct TestA s = {
.i = 1,
};
struct mScriptValue sval = mSCRIPT_MAKE_S(TestA, &s);
struct mScriptValue val;
struct mScriptFrame frame;
int32_t rval;
assert_true(mScriptObjectGet(&sval, "i0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 1);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "i1", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
mSCRIPT_PUSH(&frame.arguments, S32, 1);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 2);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "ic0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 1);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "ic0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 1);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "ic1", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s);
mSCRIPT_PUSH(&frame.arguments, S32, 1);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 2);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "ic1", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
mSCRIPT_PUSH(&frame.arguments, S32, 1);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 2);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "v0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "i0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 2);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "ic0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 2);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "v1", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
mSCRIPT_PUSH(&frame.arguments, S32, 2);
assert_true(mScriptInvoke(&val, &frame));
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "v2", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
mSCRIPT_PUSH(&frame.arguments, S32, 1);
mSCRIPT_PUSH(&frame.arguments, S32, -2);
assert_true(mScriptInvoke(&val, &frame));
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "v2", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
mSCRIPT_PUSH(&frame.arguments, S32, 1);
assert_true(mScriptInvoke(&val, &frame));
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "i0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 4);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "ic0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 4);
mScriptFrameDeinit(&frame);
assert_true(cls->init);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_DEFINE(testADynamic) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls;
assert_false(cls->init);
mScriptClassInit(cls);
assert_true(cls->init);
struct mScriptClassMember* member;
member = HashTableLookup(&cls->instanceMembers, "ifn0");
assert_non_null(member);
assert_string_equal(member->name, "ifn0");
assert_int_equal(member->type->base, mSCRIPT_TYPE_FUNCTION);
assert_int_equal(member->type->details.function.parameters.count, 1);
assert_ptr_equal(member->type->details.function.parameters.entries[0], mSCRIPT_TYPE_MS_S(TestA));
struct TestA s = {
.i = 1,
.ifn0 = testAi0,
.ifn1 = testAi1,
.icfn0 = testAic0,
.icfn1 = testAic1,
.vfn0 = testAv0,
.vfn1 = testAv1,
};
struct mScriptValue sval = mSCRIPT_MAKE_S(TestA, &s);
struct mScriptValue val;
struct mScriptFrame frame;
int32_t rval;
assert_true(mScriptObjectGet(&sval, "ifn0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 1);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "ifn1", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
mSCRIPT_PUSH(&frame.arguments, S32, 1);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 2);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "icfn0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 1);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "icfn0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 1);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "icfn1", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s);
mSCRIPT_PUSH(&frame.arguments, S32, 1);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 2);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "icfn1", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
mSCRIPT_PUSH(&frame.arguments, S32, 1);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 2);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "vfn0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "ifn0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 2);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "icfn0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 2);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "vfn1", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
mSCRIPT_PUSH(&frame.arguments, S32, 2);
assert_true(mScriptInvoke(&val, &frame));
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "ifn0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 4);
mScriptFrameDeinit(&frame);
assert_true(mScriptObjectGet(&sval, "icfn0", &val));
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CS(TestA), &s);
assert_true(mScriptInvoke(&val, &frame));
assert_true(mScriptPopS32(&frame.returnValues, &rval));
assert_int_equal(rval, 4);
mScriptFrameDeinit(&frame);
assert_true(cls->init);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_DEFINE(testBLayout) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestB)->details.cls;
assert_false(cls->init);
mScriptClassInit(cls);
assert_true(cls->init);
struct mScriptClassMember* member;
member = HashTableLookup(&cls->instanceMembers, "i");
assert_non_null(member);
assert_string_equal(member->name, "i");
assert_string_equal(member->docstring, MEMBER_A_DOCSTRING);
assert_ptr_equal(member->type, mSCRIPT_TYPE_MS_S32);
assert_int_equal(member->offset, 0);
member = HashTableLookup(&cls->instanceMembers, "i2");
assert_non_null(member);
assert_string_equal(member->name, "i2");
assert_null(member->docstring);
assert_ptr_equal(member->type, mSCRIPT_TYPE_MS_S32);
assert_int_equal(member->offset, sizeof(int32_t));
member = HashTableLookup(&cls->instanceMembers, "b8");
assert_non_null(member);
assert_string_equal(member->name, "b8");
assert_null(member->docstring);
assert_ptr_equal(member->type, mSCRIPT_TYPE_MS_S8);
assert_int_equal(member->offset, sizeof(int32_t) * 2);
member = HashTableLookup(&cls->instanceMembers, "hUnaligned");
assert_non_null(member);
assert_string_equal(member->name, "hUnaligned");
assert_null(member->docstring);
assert_ptr_equal(member->type, mSCRIPT_TYPE_MS_S16);
assert_int_not_equal(member->offset, sizeof(int32_t) * 2 + 1);
size_t hOffset = member->offset;
member = HashTableLookup(&cls->instanceMembers, "i3");
assert_non_null(member);
assert_string_equal(member->name, "i3");
assert_null(member->docstring);
assert_ptr_equal(member->type, mSCRIPT_TYPE_MS_S32);
assert_true(member->offset >= hOffset + sizeof(int16_t));
member = HashTableLookup(&cls->instanceMembers, "_super");
assert_non_null(member);
assert_string_equal(member->name, "_super");
assert_null(member->docstring);
assert_ptr_equal(member->type, mSCRIPT_TYPE_MS_S(TestA));
assert_int_equal(member->offset, 0);
member = HashTableLookup(&cls->instanceMembers, "unknown");
assert_null(member);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_DEFINE(testBGet) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestB)->details.cls;
struct TestB s = {
.d = {
.i = 1,
.i2 = 2,
.b8 = 3,
.hUnaligned = 4
},
.i3 = 5
};
struct mScriptValue sval = mSCRIPT_MAKE_S(TestB, &s);
struct mScriptValue super;
struct mScriptValue val;
struct mScriptValue compare;
compare = mSCRIPT_MAKE_S32(1);
assert_true(mScriptObjectGet(&sval, "i", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32(2);
assert_true(mScriptObjectGet(&sval, "i2", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32(3);
assert_true(mScriptObjectGet(&sval, "b8", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32(4);
assert_true(mScriptObjectGet(&sval, "hUnaligned", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32(5);
assert_true(mScriptObjectGet(&sval, "i3", &val));
assert_true(compare.type->equal(&compare, &val));
// Superclass explicit access
assert_true(mScriptObjectGet(&sval, "_super", &super));
assert_true(super.type == mSCRIPT_TYPE_MS_S(TestA));
compare = mSCRIPT_MAKE_S32(1);
assert_true(mScriptObjectGet(&super, "i", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32(2);
assert_true(mScriptObjectGet(&super, "i2", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32(3);
assert_true(mScriptObjectGet(&super, "b8", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32(4);
assert_true(mScriptObjectGet(&super, "hUnaligned", &val));
assert_true(compare.type->equal(&compare, &val));
assert_false(mScriptObjectGet(&super, "i3", &val));
// Test const-correctness
sval = mSCRIPT_MAKE_CS(TestB, &s);
assert_true(mScriptObjectGet(&sval, "_super", &super));
assert_true(super.type == mSCRIPT_TYPE_MS_CS(TestA));
assert_true(cls->init);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_DEFINE(testBSet) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestB)->details.cls;
struct TestB s = {
.d = {
.i = 1,
.i2 = 2,
.b8 = 3,
.hUnaligned = 4
},
.i3 = 5
};
struct mScriptValue sval = mSCRIPT_MAKE_S(TestB, &s);
struct mScriptValue super;
struct mScriptValue val;
val = mSCRIPT_MAKE_S32(2);
assert_true(mScriptObjectSet(&sval, "i", &val));
assert_int_equal(s.d.i, 2);
val = mSCRIPT_MAKE_S32(3);
assert_true(mScriptObjectSet(&sval, "i2", &val));
assert_int_equal(s.d.i2, 3);
val = mSCRIPT_MAKE_S32(4);
assert_true(mScriptObjectSet(&sval, "b8", &val));
assert_int_equal(s.d.b8, 4);
val = mSCRIPT_MAKE_S32(5);
assert_true(mScriptObjectSet(&sval, "hUnaligned", &val));
assert_int_equal(s.d.hUnaligned, 5);
val = mSCRIPT_MAKE_S32(6);
assert_true(mScriptObjectSet(&sval, "i3", &val));
assert_int_equal(s.i3, 6);
// Superclass explicit access
assert_true(mScriptObjectGet(&sval, "_super", &super));
assert_true(super.type == mSCRIPT_TYPE_MS_S(TestA));
val = mSCRIPT_MAKE_S32(3);
assert_true(mScriptObjectSet(&super, "i", &val));
assert_int_equal(s.d.i, 3);
val = mSCRIPT_MAKE_S32(4);
assert_true(mScriptObjectSet(&super, "i2", &val));
assert_int_equal(s.d.i2, 4);
val = mSCRIPT_MAKE_S32(5);
assert_true(mScriptObjectSet(&super, "b8", &val));
assert_int_equal(s.d.b8, 5);
val = mSCRIPT_MAKE_S32(6);
assert_true(mScriptObjectSet(&super, "hUnaligned", &val));
assert_int_equal(s.d.hUnaligned, 6);
val = mSCRIPT_MAKE_S32(7);
assert_false(mScriptObjectSet(&super, "i3", &val));
assert_int_equal(s.i3, 6);
// Const access
sval = mSCRIPT_MAKE_CS(TestB, &s);
val = mSCRIPT_MAKE_S32(4);
assert_false(mScriptObjectSet(&sval, "i", &val));
assert_int_equal(s.d.i, 3);
val = mSCRIPT_MAKE_S32(5);
assert_false(mScriptObjectSet(&sval, "i2", &val));
assert_int_equal(s.d.i2, 4);
val = mSCRIPT_MAKE_S32(6);
assert_false(mScriptObjectSet(&sval, "b8", &val));
assert_int_equal(s.d.b8, 5);
val = mSCRIPT_MAKE_S32(7);
assert_false(mScriptObjectSet(&sval, "hUnaligned", &val));
assert_int_equal(s.d.hUnaligned, 6);
val = mSCRIPT_MAKE_S32(8);
assert_false(mScriptObjectSet(&sval, "i3", &val));
assert_int_equal(s.i3, 6);
assert_true(cls->init);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_DEFINE(testDLayout) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestD)->details.cls;
assert_false(cls->init);
mScriptClassInit(cls);
assert_true(cls->init);
struct mScriptClassMember* member;
member = HashTableLookup(&cls->instanceMembers, "a");
assert_non_null(member);
assert_string_equal(member->name, "a");
assert_null(member->docstring);
assert_ptr_equal(member->type, mSCRIPT_TYPE_MS_S(TestC));
assert_int_equal(member->offset, 0);
member = HashTableLookup(&cls->instanceMembers, "b");
assert_non_null(member);
assert_string_equal(member->name, "b");
assert_null(member->docstring);
assert_ptr_equal(member->type, mSCRIPT_TYPE_MS_S(TestC));
assert_int_equal(member->offset, sizeof(struct TestC));
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_DEFINE(testDGet) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestD)->details.cls;
struct TestD s = {
.a = { 1 },
.b = { 2 },
};
struct mScriptValue sval = mSCRIPT_MAKE_S(TestD, &s);
struct mScriptValue val;
struct mScriptValue member;
struct mScriptValue compare;
compare = mSCRIPT_MAKE_S32(1);
assert_true(mScriptObjectGet(&sval, "a", &member));
assert_true(mScriptObjectGet(&member, "i", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32(2);
assert_true(mScriptObjectGet(&sval, "b", &member));
assert_true(mScriptObjectGet(&member, "i", &val));
assert_true(compare.type->equal(&compare, &val));
assert_true(cls->init);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_DEFINE(testDSet) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestD)->details.cls;
struct TestD s = {
.a = { 1 },
.b = { 2 },
};
struct mScriptValue sval = mSCRIPT_MAKE_S(TestD, &s);
struct mScriptValue member;
struct mScriptValue val;
val = mSCRIPT_MAKE_S32(2);
assert_true(mScriptObjectGet(&sval, "a", &member));
assert_true(mScriptObjectSet(&member, "i", &val));
assert_int_equal(s.a.i, 2);
assert_int_equal(s.b.i, 2);
val = mSCRIPT_MAKE_S32(3);
assert_true(mScriptObjectGet(&sval, "b", &member));
assert_true(mScriptObjectSet(&member, "i", &val));
assert_int_equal(s.a.i, 2);
assert_int_equal(s.b.i, 3);
sval = mSCRIPT_MAKE_CS(TestD, &s);
val = mSCRIPT_MAKE_S32(4);
assert_true(mScriptObjectGet(&sval, "a", &member));
assert_false(mScriptObjectSet(&member, "i", &val));
assert_int_equal(s.a.i, 2);
assert_int_equal(s.b.i, 3);
val = mSCRIPT_MAKE_S32(5);
assert_true(mScriptObjectGet(&sval, "b", &member));
assert_false(mScriptObjectSet(&member, "i", &val));
assert_int_equal(s.a.i, 2);
assert_int_equal(s.b.i, 3);
assert_true(cls->init);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_DEFINE(testEGet) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestE)->details.cls;
struct TestE s = {
};
struct mScriptValue sval = mSCRIPT_MAKE_S(TestE, &s);
struct mScriptValue val;
struct mScriptValue compare;
compare = mSCRIPT_MAKE_S32('a');
assert_true(mScriptObjectGet(&sval, "a", &val));
assert_true(compare.type->equal(&compare, &val));
compare = mSCRIPT_MAKE_S32('b');
assert_true(mScriptObjectGet(&sval, "b", &val));
assert_true(compare.type->equal(&compare, &val));
assert_true(cls->init);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_DEFINE(testFDeinit) {
struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestF)->details.cls;
int ref = 0;
struct TestF* s = calloc(1, sizeof(struct TestF));
s->ref = &ref;
struct mScriptValue* val = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(TestF));
val->value.opaque = s;
val->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
mScriptValueDeref(val);
assert_int_equal(ref, 1);
assert_true(cls->init);
mScriptClassDeinit(cls);
assert_false(cls->init);
}
M_TEST_SUITE_DEFINE(mScriptClasses,
cmocka_unit_test(testALayout),
cmocka_unit_test(testASignatures),
cmocka_unit_test(testAGet),
cmocka_unit_test(testASet),
cmocka_unit_test(testAStatic),
cmocka_unit_test(testADynamic),
cmocka_unit_test(testBLayout),
cmocka_unit_test(testBGet),
cmocka_unit_test(testBSet),
cmocka_unit_test(testDLayout),
cmocka_unit_test(testDGet),
cmocka_unit_test(testDSet),
cmocka_unit_test(testEGet),
cmocka_unit_test(testFDeinit),
)

637
src/script/test/lua.c Normal file
View File

@ -0,0 +1,637 @@
/* Copyright (c) 2013-2022 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/internal/script/lua.h>
#include <mgba/script/macros.h>
#define SETUP_LUA \
struct mScriptContext context; \
mScriptContextInit(&context); \
struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA)
#define LOAD_PROGRAM(PROG) \
do { \
struct VFile* vf = VFileFromConstMemory(PROG, strlen(PROG)); \
assert_true(lua->load(lua, NULL, vf)); \
vf->close(vf); \
} while(0)
#define TEST_PROGRAM(PROG) \
LOAD_PROGRAM(PROG); \
assert_true(lua->run(lua)); \
struct Test {
int32_t i;
int32_t (*ifn0)(struct Test*);
int32_t (*ifn1)(struct Test*, int);
void (*vfn0)(struct Test*);
void (*vfn1)(struct Test*, int);
int32_t (*icfn0)(const struct Test*);
};
static int identityInt(int in) {
return in;
}
static int addInts(int a, int b) {
return a + b;
}
static int32_t testI0(struct Test* a) {
return a->i;
}
static int32_t testI1(struct Test* a, int b) {
return a->i + b;
}
static int32_t testIC0(const struct Test* a) {
return a->i;
}
static void testV0(struct Test* a) {
++a->i;
}
static void testV1(struct Test* a, int b) {
a->i += b;
}
mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32, a);
mSCRIPT_BIND_FUNCTION(boundAddInts, S32, addInts, 2, S32, a, S32, b);
mSCRIPT_DECLARE_STRUCT(Test);
mSCRIPT_DECLARE_STRUCT_D_METHOD(Test, S32, ifn0, 0);
mSCRIPT_DECLARE_STRUCT_D_METHOD(Test, S32, ifn1, 1, S32, b);
mSCRIPT_DECLARE_STRUCT_CD_METHOD(Test, S32, icfn0, 0);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(Test, vfn0, 0);
mSCRIPT_DECLARE_STRUCT_VOID_D_METHOD(Test, vfn1, 1, S32, b);
mSCRIPT_DECLARE_STRUCT_METHOD(Test, S32, i0, testI0, 0);
mSCRIPT_DECLARE_STRUCT_METHOD(Test, S32, i1, testI1, 1, S32, b);
mSCRIPT_DECLARE_STRUCT_C_METHOD(Test, S32, ic0, testIC0, 0);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(Test, v0, testV0, 0);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(Test, v1, testV1, 1, S32, b);
mSCRIPT_DEFINE_STRUCT(Test)
mSCRIPT_DEFINE_STRUCT_MEMBER(Test, S32, i)
mSCRIPT_DEFINE_STRUCT_METHOD(Test, ifn0)
mSCRIPT_DEFINE_STRUCT_METHOD(Test, ifn1)
mSCRIPT_DEFINE_STRUCT_METHOD(Test, icfn0)
mSCRIPT_DEFINE_STRUCT_METHOD(Test, vfn0)
mSCRIPT_DEFINE_STRUCT_METHOD(Test, vfn1)
mSCRIPT_DEFINE_STRUCT_METHOD(Test, i0)
mSCRIPT_DEFINE_STRUCT_METHOD(Test, i1)
mSCRIPT_DEFINE_STRUCT_METHOD(Test, ic0)
mSCRIPT_DEFINE_STRUCT_METHOD(Test, v0)
mSCRIPT_DEFINE_STRUCT_METHOD(Test, v1)
mSCRIPT_DEFINE_END;
M_TEST_SUITE_SETUP(mScriptLua) {
if (mSCRIPT_ENGINE_LUA->init) {
mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA);
}
return 0;
}
M_TEST_SUITE_TEARDOWN(mScriptLua) {
if (mSCRIPT_ENGINE_LUA->deinit) {
mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA);
}
return 0;
}
M_TEST_DEFINE(create) {
struct mScriptContext context;
mScriptContextInit(&context);
struct mScriptEngineContext* lua = mSCRIPT_ENGINE_LUA->create(mSCRIPT_ENGINE_LUA, &context);
lua->destroy(lua);
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(loadGood) {
struct mScriptContext context;
mScriptContextInit(&context);
struct mScriptEngineContext* lua = mSCRIPT_ENGINE_LUA->create(mSCRIPT_ENGINE_LUA, &context);
const char* program = "-- test\n";
struct VFile* vf = VFileFromConstMemory(program, strlen(program));
assert_true(lua->load(lua, NULL, vf));
lua->destroy(lua);
mScriptContextDeinit(&context);
vf->close(vf);
}
M_TEST_DEFINE(loadBadSyntax) {
struct mScriptContext context;
mScriptContextInit(&context);
struct mScriptEngineContext* lua = mSCRIPT_ENGINE_LUA->create(mSCRIPT_ENGINE_LUA, &context);
const char* program = "Invalid syntax! )\n";
struct VFile* vf = VFileFromConstMemory(program, strlen(program));
assert_false(lua->load(lua, NULL, vf));
lua->destroy(lua);
mScriptContextDeinit(&context);
vf->close(vf);
}
M_TEST_DEFINE(runNop) {
SETUP_LUA;
LOAD_PROGRAM("return");
assert_true(lua->run(lua));
// Make sure we can run it twice
assert_true(lua->run(lua));
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(getGlobal) {
SETUP_LUA;
struct mScriptValue a = mSCRIPT_MAKE_S32(1);
struct mScriptValue* val;
TEST_PROGRAM("a = 1");
val = lua->getGlobal(lua, "a");
assert_non_null(val);
assert_true(a.type->equal(&a, val));
mScriptValueDeref(val);
TEST_PROGRAM("b = 1");
val = lua->getGlobal(lua, "a");
assert_non_null(val);
assert_true(a.type->equal(&a, val));
mScriptValueDeref(val);
val = lua->getGlobal(lua, "b");
assert_non_null(val);
assert_true(a.type->equal(&a, val));
mScriptValueDeref(val);
a = mSCRIPT_MAKE_S32(2);
TEST_PROGRAM("a = 2");
val = lua->getGlobal(lua, "a");
assert_non_null(val);
assert_true(a.type->equal(&a, val));
mScriptValueDeref(val);
a = mSCRIPT_MAKE_S32(3);
TEST_PROGRAM("b = a + b");
val = lua->getGlobal(lua, "b");
assert_non_null(val);
assert_true(a.type->equal(&a, val));
mScriptValueDeref(val);
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(setGlobal) {
SETUP_LUA;
struct mScriptValue a = mSCRIPT_MAKE_S32(1);
struct mScriptValue* val;
LOAD_PROGRAM("a = b");
assert_true(lua->setGlobal(lua, "b", &a));
val = lua->getGlobal(lua, "b");
assert_non_null(val);
assert_true(a.type->equal(&a, val));
mScriptValueDeref(val);
assert_true(lua->run(lua));
val = lua->getGlobal(lua, "a");
assert_non_null(val);
assert_true(a.type->equal(&a, val));
a = mSCRIPT_MAKE_S32(2);
assert_false(a.type->equal(&a, val));
mScriptValueDeref(val);
val = lua->getGlobal(lua, "a");
assert_non_null(val);
assert_false(a.type->equal(&a, val));
mScriptValueDeref(val);
assert_true(lua->setGlobal(lua, "b", &a));
val = lua->getGlobal(lua, "b");
assert_non_null(val);
assert_true(a.type->equal(&a, val));
mScriptValueDeref(val);
assert_true(lua->run(lua));
val = lua->getGlobal(lua, "a");
assert_non_null(val);
assert_true(a.type->equal(&a, val));
mScriptValueDeref(val);
assert_true(lua->setGlobal(lua, "b", NULL));
val = lua->getGlobal(lua, "b");
assert_null(val);
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(callLuaFunc) {
SETUP_LUA;
struct mScriptValue* fn;
TEST_PROGRAM("function a(b) return b + 1 end; function c(d, e) return d + e end");
assert_null(lua->getError(lua));
fn = lua->getGlobal(lua, "a");
assert_non_null(fn);
assert_int_equal(fn->type->base, mSCRIPT_TYPE_FUNCTION);
struct mScriptFrame frame;
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S32, 1);
assert_true(mScriptInvoke(fn, &frame));
int64_t val;
assert_true(mScriptPopS64(&frame.returnValues, &val));
assert_int_equal(val, 2);
mScriptFrameDeinit(&frame);
mScriptValueDeref(fn);
fn = lua->getGlobal(lua, "c");
assert_non_null(fn);
assert_int_equal(fn->type->base, mSCRIPT_TYPE_FUNCTION);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S32, 1);
mSCRIPT_PUSH(&frame.arguments, S32, 2);
assert_true(mScriptInvoke(fn, &frame));
assert_true(mScriptPopS64(&frame.returnValues, &val));
assert_int_equal(val, 3);
mScriptFrameDeinit(&frame);
mScriptValueDeref(fn);
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(callCFunc) {
SETUP_LUA;
struct mScriptValue a = mSCRIPT_MAKE_S32(1);
struct mScriptValue* val;
assert_true(lua->setGlobal(lua, "b", &boundIdentityInt));
assert_true(lua->setGlobal(lua, "d", &boundAddInts));
TEST_PROGRAM("a = b(1); c = d(1, 2)");
assert_null(lua->getError(lua));
val = lua->getGlobal(lua, "a");
assert_non_null(val);
assert_true(a.type->equal(&a, val));
mScriptValueDeref(val);
a = mSCRIPT_MAKE_S32(3);
val = lua->getGlobal(lua, "c");
assert_non_null(val);
assert_true(a.type->equal(&a, val));
mScriptValueDeref(val);
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(globalStructFieldGet) {
SETUP_LUA;
struct Test s = {
.i = 1,
};
struct mScriptValue a;
struct mScriptValue b;
struct mScriptValue* val;
LOAD_PROGRAM("b = a.i");
a = mSCRIPT_MAKE_S(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
s.i = 1;
assert_true(lua->run(lua));
b = mSCRIPT_MAKE_S32(1);
val = lua->getGlobal(lua, "b");
assert_non_null(val);
assert_true(b.type->equal(&b, val));
mScriptValueDeref(val);
s.i = 2;
assert_true(lua->run(lua));
b = mSCRIPT_MAKE_S32(2);
val = lua->getGlobal(lua, "b");
assert_non_null(val);
assert_true(b.type->equal(&b, val));
mScriptValueDeref(val);
a = mSCRIPT_MAKE_CS(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
s.i = 1;
assert_true(lua->run(lua));
b = mSCRIPT_MAKE_S32(1);
val = lua->getGlobal(lua, "b");
assert_non_null(val);
assert_true(b.type->equal(&b, val));
mScriptValueDeref(val);
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(globalStructFieldSet) {
SETUP_LUA;
struct Test s = {
.i = 1,
};
struct mScriptValue a;
struct mScriptValue b;
LOAD_PROGRAM("a.i = b");
a = mSCRIPT_MAKE_S(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
s.i = 1;
b = mSCRIPT_MAKE_S32(2);
assert_true(lua->setGlobal(lua, "b", &b));
assert_true(lua->run(lua));
assert_int_equal(s.i, 2);
a = mSCRIPT_MAKE_CS(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
s.i = 1;
b = mSCRIPT_MAKE_S32(2);
assert_true(lua->setGlobal(lua, "b", &b));
assert_false(lua->run(lua));
assert_int_equal(s.i, 1);
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(globalStructMethods) {
SETUP_LUA;
struct Test s = {
.i = 1,
.ifn0 = testI0,
.ifn1 = testI1,
.icfn0 = testIC0,
.vfn0 = testV0,
.vfn1 = testV1,
};
struct mScriptValue a;
struct mScriptValue b;
struct mScriptValue* val;
// ifn0
LOAD_PROGRAM("b = a:ifn0()");
a = mSCRIPT_MAKE_S(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
s.i = 1;
assert_true(lua->run(lua));
b = mSCRIPT_MAKE_S32(1);
val = lua->getGlobal(lua, "b");
assert_non_null(val);
assert_true(b.type->equal(&b, val));
mScriptValueDeref(val);
a = mSCRIPT_MAKE_CS(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
assert_false(lua->run(lua));
// ifn1
LOAD_PROGRAM("b = a:ifn1(c)");
a = mSCRIPT_MAKE_S(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
s.i = 1;
b = mSCRIPT_MAKE_S32(1);
assert_true(lua->setGlobal(lua, "c", &b));
assert_true(lua->run(lua));
b = mSCRIPT_MAKE_S32(2);
val = lua->getGlobal(lua, "b");
assert_non_null(val);
assert_true(b.type->equal(&b, val));
mScriptValueDeref(val);
b = mSCRIPT_MAKE_S32(2);
assert_true(lua->setGlobal(lua, "c", &b));
assert_true(lua->run(lua));
b = mSCRIPT_MAKE_S32(3);
val = lua->getGlobal(lua, "b");
assert_non_null(val);
assert_true(b.type->equal(&b, val));
mScriptValueDeref(val);
a = mSCRIPT_MAKE_CS(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
assert_false(lua->run(lua));
// vfn0
LOAD_PROGRAM("a:vfn0()");
a = mSCRIPT_MAKE_S(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
s.i = 1;
assert_true(lua->run(lua));
assert_int_equal(s.i, 2);
assert_true(lua->run(lua));
assert_int_equal(s.i, 3);
a = mSCRIPT_MAKE_CS(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
assert_false(lua->run(lua));
// vfn1
LOAD_PROGRAM("a:vfn1(c)");
a = mSCRIPT_MAKE_S(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
s.i = 1;
b = mSCRIPT_MAKE_S32(1);
assert_true(lua->setGlobal(lua, "c", &b));
assert_true(lua->run(lua));
assert_int_equal(s.i, 2);
b = mSCRIPT_MAKE_S32(2);
assert_true(lua->setGlobal(lua, "c", &b));
s.i = 1;
assert_true(lua->run(lua));
assert_int_equal(s.i, 3);
a = mSCRIPT_MAKE_CS(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
s.i = 1;
b = mSCRIPT_MAKE_S32(1);
assert_true(lua->setGlobal(lua, "c", &b));
assert_false(lua->run(lua));
assert_int_equal(s.i, 1);
// icfn0
LOAD_PROGRAM("b = a:icfn0()");
a = mSCRIPT_MAKE_S(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
s.i = 1;
assert_true(lua->run(lua));
b = mSCRIPT_MAKE_S32(1);
val = lua->getGlobal(lua, "b");
assert_non_null(val);
assert_true(b.type->equal(&b, val));
mScriptValueDeref(val);
a = mSCRIPT_MAKE_CS(Test, &s);
assert_true(lua->setGlobal(lua, "a", &a));
s.i = 1;
assert_true(lua->run(lua));
b = mSCRIPT_MAKE_S32(1);
val = lua->getGlobal(lua, "b");
assert_non_null(val);
assert_true(b.type->equal(&b, val));
mScriptValueDeref(val);
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(errorReporting) {
SETUP_LUA;
assert_null(lua->getError(lua));
LOAD_PROGRAM("assert(false)");
assert_false(lua->run(lua));
const char* errorBuffer = lua->getError(lua);
assert_non_null(errorBuffer);
assert_non_null(strstr(errorBuffer, "assertion failed"));
LOAD_PROGRAM("assert(true)");
assert_true(lua->run(lua));
assert_null(lua->getError(lua));
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(tableLookup) {
SETUP_LUA;
assert_null(lua->getError(lua));
struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
assert_non_null(table);
struct mScriptValue* val;
mScriptContextSetGlobal(&context, "t", table);
val = mScriptValueAlloc(mSCRIPT_TYPE_MS_S64);
val->value.s64 = 0;
assert_true(mScriptTableInsert(table, &mSCRIPT_MAKE_S64(0), val));
mScriptValueDeref(val);
val = mScriptStringCreateFromASCII("t");
assert_true(mScriptTableInsert(table, &mSCRIPT_MAKE_CHARP("t"), val));
mScriptValueDeref(val);
val = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
assert_true(mScriptTableInsert(table, &mSCRIPT_MAKE_CHARP("sub"), val));
mScriptValueDeref(val);
table = val;
val = mScriptStringCreateFromASCII("t");
assert_true(mScriptTableInsert(table, &mSCRIPT_MAKE_CHARP("t"), val));
mScriptValueDeref(val);
TEST_PROGRAM("assert(t)");
TEST_PROGRAM("assert(t['t'] ~= nil)");
TEST_PROGRAM("assert(t['t'] == 't')");
TEST_PROGRAM("assert(t.t == 't')");
TEST_PROGRAM("assert(t['x'] == nil)");
TEST_PROGRAM("assert(t.x == nil)");
TEST_PROGRAM("assert(t.sub ~= nil)");
TEST_PROGRAM("assert(t.sub.t ~= nil)");
TEST_PROGRAM("assert(t.sub.t == 't')");
TEST_PROGRAM("assert(t[0] ~= nil)");
TEST_PROGRAM("assert(t[0] == 0)");
TEST_PROGRAM("assert(t[1] == nil)");
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(tableIterate) {
SETUP_LUA;
assert_null(lua->getError(lua));
struct mScriptValue* table = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
assert_non_null(table);
struct mScriptValue* val;
struct mScriptValue* key;
mScriptContextSetGlobal(&context, "t", table);
int i;
for (i = 0; i < 50; ++i) {
val = mScriptValueAlloc(mSCRIPT_TYPE_MS_S64);
val->value.s64 = 1LL << i;
key = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32);
key->value.s32 = i;
assert_true(mScriptTableInsert(table, key, val));
mScriptValueDeref(key);
mScriptValueDeref(val);
}
assert_int_equal(mScriptTableSize(table), 50);
TEST_PROGRAM("assert(t)");
TEST_PROGRAM("assert(#t == 50)");
TEST_PROGRAM(
"i = 0\n"
"z = 0\n"
"for k, v in pairs(t) do\n"
" i = i + 1\n"
" z = z + v\n"
" assert((1 << k) == v)\n"
"end\n"
);
TEST_PROGRAM("assert(i == #t)");
TEST_PROGRAM("assert(z == (1 << #t) - 1)");
mScriptContextDeinit(&context);
}
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptLua,
cmocka_unit_test(create),
cmocka_unit_test(loadGood),
cmocka_unit_test(loadBadSyntax),
cmocka_unit_test(runNop),
cmocka_unit_test(getGlobal),
cmocka_unit_test(setGlobal),
cmocka_unit_test(callLuaFunc),
cmocka_unit_test(callCFunc),
cmocka_unit_test(globalStructFieldGet),
cmocka_unit_test(globalStructFieldSet),
cmocka_unit_test(globalStructMethods),
cmocka_unit_test(errorReporting),
cmocka_unit_test(tableLookup),
cmocka_unit_test(tableIterate),
)

951
src/script/test/types.c Normal file
View File

@ -0,0 +1,951 @@
/* 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/context.h>
#include <mgba/script/macros.h>
#include <mgba/script/types.h>
struct Test {
int32_t a;
};
mSCRIPT_DEFINE_STRUCT(Test)
mSCRIPT_DEFINE_END;
static int voidOne(void) {
return 1;
}
static void discard(int ignored) {
UNUSED(ignored);
}
static int identityInt(int in) {
return in;
}
static int64_t identityInt64(int64_t 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;
}
static int isHello(const char* str) {
return strcmp(str, "hello") == 0;
}
mSCRIPT_BIND_FUNCTION(boundVoidOne, S32, voidOne, 0);
mSCRIPT_BIND_VOID_FUNCTION(boundDiscard, discard, 1, S32, ignored);
mSCRIPT_BIND_FUNCTION(boundIdentityInt, S32, identityInt, 1, S32, in);
mSCRIPT_BIND_FUNCTION(boundIdentityInt64, S64, identityInt64, 1, S64, in);
mSCRIPT_BIND_FUNCTION(boundIdentityFloat, F32, identityFloat, 1, F32, in);
mSCRIPT_BIND_FUNCTION(boundIdentityStruct, S(Test), identityStruct, 1, S(Test), t);
mSCRIPT_BIND_FUNCTION(boundAddInts, S32, addInts, 2, S32, a, S32, b);
mSCRIPT_BIND_FUNCTION(boundSubInts, S32, subInts, 2, S32, a, S32, b);
mSCRIPT_BIND_FUNCTION(boundIsHello, S32, isHello, 1, CHARP, str);
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(identityFunctionS64) {
struct mScriptFrame frame;
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S64, 1);
assert_true(mScriptInvoke(&boundIdentityInt64, &frame));
int64_t val;
assert_true(mScriptPopS64(&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(wrongPopType) {
struct mScriptFrame frame;
int32_t s32;
int64_t s64;
uint32_t u32;
uint64_t u64;
float f32;
double f64;
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S32, 0);
assert_false(mScriptPopU32(&frame.arguments, &u32));
assert_false(mScriptPopF32(&frame.arguments, &f32));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S64, 0);
assert_false(mScriptPopU64(&frame.arguments, &u64));
assert_false(mScriptPopF64(&frame.arguments, &f64));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, U32, 0);
assert_false(mScriptPopS32(&frame.arguments, &s32));
assert_false(mScriptPopF32(&frame.arguments, &f32));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, U64, 0);
assert_false(mScriptPopS64(&frame.arguments, &s64));
assert_false(mScriptPopF64(&frame.arguments, &f64));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, F32, 0);
assert_false(mScriptPopS32(&frame.arguments, &s32));
assert_false(mScriptPopU32(&frame.arguments, &u32));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, F64, 0);
assert_false(mScriptPopS64(&frame.arguments, &s64));
assert_false(mScriptPopU64(&frame.arguments, &u64));
mScriptFrameDeinit(&frame);
}
M_TEST_DEFINE(wrongPopSize) {
struct mScriptFrame frame;
int32_t s32;
int64_t s64;
uint32_t u32;
uint64_t u64;
float f32;
double f64;
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S32, 0);
assert_false(mScriptPopS64(&frame.arguments, &s64));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S64, 0);
assert_false(mScriptPopS32(&frame.arguments, &s32));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, U32, 0);
assert_false(mScriptPopU64(&frame.arguments, &u64));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, U64, 0);
assert_false(mScriptPopU32(&frame.arguments, &u32));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, F32, 0);
assert_false(mScriptPopF64(&frame.arguments, &f64));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, F64, 0);
assert_false(mScriptPopF32(&frame.arguments, &f32));
mScriptFrameDeinit(&frame);
}
bool mScriptPopCSTest(struct mScriptList* list, const struct Test** out) {
mSCRIPT_POP(list, CS(Test), val);
*out = val;
return true;
}
bool mScriptPopSTest(struct mScriptList* list, struct Test** out) {
mSCRIPT_POP(list, S(Test), val);
*out = val;
return true;
}
M_TEST_DEFINE(wrongConst) {
struct mScriptFrame frame;
struct Test a;
struct Test* b;
const struct Test* cb;
struct mScriptTypeTuple signature = {
.count = 1,
.variable = false
};
mScriptClassInit(mSCRIPT_TYPE_MS_S(Test)->details.cls);
mScriptClassInit(mSCRIPT_TYPE_MS_CS(Test)->details.cls);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(Test), &a);
signature.entries[0] = mSCRIPT_TYPE_MS_S(Test);
assert_true(mScriptCoerceFrame(&signature, &frame.arguments));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CS(Test), &a);
signature.entries[0] = mSCRIPT_TYPE_MS_CS(Test);
assert_true(mScriptCoerceFrame(&signature, &frame.arguments));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(Test), &a);
signature.entries[0] = mSCRIPT_TYPE_MS_CS(Test);
assert_true(mScriptCoerceFrame(&signature, &frame.arguments));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CS(Test), &a);
signature.entries[0] = mSCRIPT_TYPE_MS_S(Test);
assert_false(mScriptCoerceFrame(&signature, &frame.arguments));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(Test), &a);
assert_true(mScriptPopSTest(&frame.arguments, &b));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S(Test), &a);
assert_false(mScriptPopCSTest(&frame.arguments, &cb));
signature.entries[0] = mSCRIPT_TYPE_MS_CS(Test);
assert_true(mScriptCoerceFrame(&signature, &frame.arguments));
assert_true(mScriptPopCSTest(&frame.arguments, &cb));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CS(Test), &a);
assert_false(mScriptPopSTest(&frame.arguments, &b));
signature.entries[0] = mSCRIPT_TYPE_MS_S(Test);
assert_false(mScriptCoerceFrame(&signature, &frame.arguments));
assert_false(mScriptPopSTest(&frame.arguments, &b));
mScriptFrameDeinit(&frame);
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CS(Test), &a);
assert_true(mScriptPopCSTest(&frame.arguments, &cb));
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(coerceWiden) {
struct mScriptFrame frame;
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S32, -1);
assert_true(mScriptInvoke(&boundIdentityInt64, &frame));
int64_t val;
assert_true(mScriptPopS64(&frame.returnValues, &val));
assert_true(val == -1LL);
mScriptFrameDeinit(&frame);
}
M_TEST_DEFINE(coerceNarrow) {
struct mScriptFrame frame;
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, S64, -1);
assert_true(mScriptInvoke(&boundIdentityInt, &frame));
int32_t val;
assert_true(mScriptPopS32(&frame.returnValues, &val));
assert_true(val == -1);
mScriptFrameDeinit(&frame);
}
#define COMPARE_BOOL(EXPECT, T0, V0, T1, V1) \
a = mSCRIPT_MAKE_ ## T0 (V0); \
b = mSCRIPT_MAKE_ ## T1 (V1); \
assert_ ## EXPECT (a.type->equal(&a, &b));
M_TEST_DEFINE(s32Equality) {
struct mScriptValue a;
struct mScriptValue b;
// S32
COMPARE_BOOL(true, S32, 0, S32, 0);
COMPARE_BOOL(false, S32, 0, S32, 1);
COMPARE_BOOL(true, S32, 1, S32, 1);
COMPARE_BOOL(false, S32, 1, S32, -1);
COMPARE_BOOL(true, S32, -1, S32, -1);
// S64
COMPARE_BOOL(true, S32, 0, S64, 0);
COMPARE_BOOL(false, S32, 0, S64, 1);
COMPARE_BOOL(true, S32, 1, S64, 1);
COMPARE_BOOL(false, S32, 1, S64, -1);
COMPARE_BOOL(true, S32, -1, S64, -1);
COMPARE_BOOL(false, S32, 0, S64, 0x100000000LL);
COMPARE_BOOL(false, S32, -1, S64, 0x1FFFFFFFFLL);
COMPARE_BOOL(false, S32, -1, S64, -0x100000001LL);
// U32
COMPARE_BOOL(true, S32, 0, U32, 0);
COMPARE_BOOL(false, S32, 0, U32, 1);
COMPARE_BOOL(true, S32, 1, U32, 1);
COMPARE_BOOL(true, S32, 0x7FFFFFFF, U32, 0x7FFFFFFFU);
COMPARE_BOOL(false, S32, 0xFFFFFFFF, U32, 0xFFFFFFFFU);
COMPARE_BOOL(false, S32, 0x80000000, U32, 0x80000000U);
// U64
COMPARE_BOOL(true, S32, 0, U64, 0);
COMPARE_BOOL(false, S32, 0, U64, 1);
COMPARE_BOOL(true, S32, 1, U64, 1);
COMPARE_BOOL(true, S32, 0x7FFFFFFF, U64, 0x7FFFFFFFULL);
COMPARE_BOOL(false, S32, 0xFFFFFFFF, U64, 0xFFFFFFFFULL);
COMPARE_BOOL(false, S32, 0x80000000, U64, 0x80000000ULL);
// F32
COMPARE_BOOL(true, S32, 0, F32, 0);
COMPARE_BOOL(false, S32, 1, F32, 0);
COMPARE_BOOL(false, S32, 0, F32, 1);
COMPARE_BOOL(true, S32, 1, F32, 1);
COMPARE_BOOL(false, S32, 0, F32, -1);
COMPARE_BOOL(false, S32, 1, F32, -1);
COMPARE_BOOL(true, S32, -1, F32, -1);
COMPARE_BOOL(false, S32, 1, F32, 1.1);
COMPARE_BOOL(false, S32, 0, F32, 0.1);
COMPARE_BOOL(true, S32, 0x40000000, F32, 0x40000000);
COMPARE_BOOL(true, S32, -0x40000000, F32, -0x40000000);
// F64
COMPARE_BOOL(true, S32, 0, F64, 0);
COMPARE_BOOL(false, S32, 1, F64, 0);
COMPARE_BOOL(false, S32, 0, F64, 1);
COMPARE_BOOL(true, S32, 1, F64, 1);
COMPARE_BOOL(false, S32, 0, F64, -1);
COMPARE_BOOL(false, S32, 1, F64, -1);
COMPARE_BOOL(true, S32, -1, F64, -1);
COMPARE_BOOL(false, S32, 1, F64, 1.1);
COMPARE_BOOL(false, S32, 0, F64, 0.1);
COMPARE_BOOL(true, S32, 0x40000000, F64, 0x40000000);
COMPARE_BOOL(true, S32, -0x40000000, F64, -0x40000000);
}
M_TEST_DEFINE(s64Equality) {
struct mScriptValue a;
struct mScriptValue b;
// S32
COMPARE_BOOL(true, S64, 0, S32, 0);
COMPARE_BOOL(false, S64, 0, S32, 1);
COMPARE_BOOL(true, S64, 1, S32, 1);
COMPARE_BOOL(false, S64, 1, S32, -1);
COMPARE_BOOL(true, S64, -1, S32, -1);
COMPARE_BOOL(false, S64, 0x100000000LL, S32, 0);
COMPARE_BOOL(false, S64, 0x1FFFFFFFFLL, S32, -1);
COMPARE_BOOL(false, S64, -0x100000001LL, S32, -1);
// S64
COMPARE_BOOL(true, S64, 0, S64, 0);
COMPARE_BOOL(false, S64, 0, S64, 1);
COMPARE_BOOL(true, S64, 1, S64, 1);
COMPARE_BOOL(false, S64, 1, S64, -1);
COMPARE_BOOL(true, S64, -1, S64, -1);
COMPARE_BOOL(false, S64, 0, S64, 0x100000000LL);
COMPARE_BOOL(false, S64, -1, S64, 0x1FFFFFFFFLL);
COMPARE_BOOL(false, S64, -1, S64, -0x100000001LL);
COMPARE_BOOL(false, S64, 0x100000000LL, S64, 0);
COMPARE_BOOL(false, S64, 0x1FFFFFFFFLL, S64, -1);
COMPARE_BOOL(false, S64, -0x100000001LL, S64, -1);
COMPARE_BOOL(true, S64, 0x100000000LL, S64, 0x100000000LL);
COMPARE_BOOL(true, S64, 0x1FFFFFFFFLL, S64, 0x1FFFFFFFFLL);
COMPARE_BOOL(true, S64, -0x100000001LL, S64, -0x100000001LL);
// U32
COMPARE_BOOL(true, S64, 0, U32, 0);
COMPARE_BOOL(false, S64, 0, U32, 1);
COMPARE_BOOL(true, S64, 1, U32, 1);
COMPARE_BOOL(true, S64, 0x7FFFFFFFLL, U32, 0x7FFFFFFFU);
COMPARE_BOOL(true, S64, 0xFFFFFFFFLL, U32, 0xFFFFFFFFU);
COMPARE_BOOL(true, S64, 0x80000000LL, U32, 0x80000000U);
COMPARE_BOOL(false, S64, -1, U32, 0xFFFFFFFFU);
COMPARE_BOOL(false, S64, -0x80000000LL, U32, 0x80000000U);
// U64
COMPARE_BOOL(true, S64, 0, U64, 0);
COMPARE_BOOL(false, S64, 0, U64, 1);
COMPARE_BOOL(true, S64, 1, U64, 1);
COMPARE_BOOL(true, S64, 0x07FFFFFFFLL, U64, 0x07FFFFFFFULL);
COMPARE_BOOL(true, S64, 0x0FFFFFFFFLL, U64, 0x0FFFFFFFFULL);
COMPARE_BOOL(true, S64, 0x080000000LL, U64, 0x080000000ULL);
COMPARE_BOOL(false, S64, 0, U64, 0x100000000ULL);
COMPARE_BOOL(false, S64, 0x100000000LL, U64, 0);
COMPARE_BOOL(true, S64, 0x100000000LL, U64, 0x100000000ULL);
COMPARE_BOOL(false, S64, -1, U64, 0x0FFFFFFFFULL);
COMPARE_BOOL(false, S64, -1, U64, 0xFFFFFFFFFFFFFFFFULL);
COMPARE_BOOL(false, S64, -0x080000000LL, U64, 0x080000000ULL);
// F32
COMPARE_BOOL(true, S64, 0, F32, 0);
COMPARE_BOOL(false, S64, 1, F32, 0);
COMPARE_BOOL(false, S64, 0, F32, 1);
COMPARE_BOOL(true, S64, 1, F32, 1);
COMPARE_BOOL(false, S64, 0, F32, -1);
COMPARE_BOOL(false, S64, 1, F32, -1);
COMPARE_BOOL(true, S64, -1, F32, -1);
COMPARE_BOOL(false, S64, 1, F32, 1.1);
COMPARE_BOOL(false, S64, 0, F32, 0.1);
COMPARE_BOOL(true, S64, 0x4000000000000000LL, F32, 0x4000000000000000LL);
COMPARE_BOOL(true, S64, -0x4000000000000000LL, F32, -0x4000000000000000LL);
// F64
COMPARE_BOOL(true, S64, 0, F64, 0);
COMPARE_BOOL(false, S64, 1, F64, 0);
COMPARE_BOOL(false, S64, 0, F64, 1);
COMPARE_BOOL(true, S64, 1, F64, 1);
COMPARE_BOOL(false, S64, 0, F64, -1);
COMPARE_BOOL(false, S64, 1, F64, -1);
COMPARE_BOOL(true, S64, -1, F64, -1);
COMPARE_BOOL(false, S64, 1, F64, 1.1);
COMPARE_BOOL(false, S64, 0, F64, 0.1);
COMPARE_BOOL(true, S64, 0x4000000000000000LL, F64, 0x4000000000000000LL);
COMPARE_BOOL(true, S64, -0x4000000000000000LL, F64, -0x4000000000000000LL);
}
M_TEST_DEFINE(u32Equality) {
struct mScriptValue a;
struct mScriptValue b;
// U32
COMPARE_BOOL(true, U32, 0, U32, 0);
COMPARE_BOOL(false, U32, 0, U32, 1);
COMPARE_BOOL(true, U32, 1, U32, 1);
COMPARE_BOOL(false, U32, 0x80000000U, U32, 1);
COMPARE_BOOL(true, U32, 0x80000000U, U32, 0x80000000U);
COMPARE_BOOL(false, U32, 0x7FFFFFFFU, U32, 1);
COMPARE_BOOL(true, U32, 0x7FFFFFFFU, U32, 0x7FFFFFFFU);
// U64
COMPARE_BOOL(true, U32, 0, U64, 0);
COMPARE_BOOL(false, U32, 0, U64, 1);
COMPARE_BOOL(true, U32, 1, U64, 1);
COMPARE_BOOL(false, U32, 0x80000000U, U64, 1);
COMPARE_BOOL(true, U32, 0x80000000U, U64, 0x080000000ULL);
COMPARE_BOOL(false, U32, 0x7FFFFFFFU, U64, 1);
COMPARE_BOOL(true, U32, 0x7FFFFFFFU, U64, 0x07FFFFFFFULL);
COMPARE_BOOL(false, U32, 0x80000000U, U64, 0x180000000ULL);
COMPARE_BOOL(false, U32, 0, U64, 0x100000000ULL);
// S32
COMPARE_BOOL(true, U32, 0, S32, 0);
COMPARE_BOOL(false, U32, 0, S32, 1);
COMPARE_BOOL(true, U32, 1, S32, 1);
COMPARE_BOOL(true, U32, 0x7FFFFFFFU, S32, 0x7FFFFFFF);
COMPARE_BOOL(false, U32, 0xFFFFFFFFU, S32, 0xFFFFFFFF);
COMPARE_BOOL(false, U32, 0x80000000U, S32, 0x80000000);
// S64
COMPARE_BOOL(true, U32, 0, S64, 0);
COMPARE_BOOL(false, U32, 0, S64, 1);
COMPARE_BOOL(true, U32, 1, S64, 1);
COMPARE_BOOL(true, U32, 0x7FFFFFFFU, S64, 0x07FFFFFFFLL);
COMPARE_BOOL(true, U32, 0xFFFFFFFFU, S64, 0x0FFFFFFFFLL);
COMPARE_BOOL(true, U32, 0x80000000U, S64, 0x080000000LL);
COMPARE_BOOL(false, U32, 0x80000000U, S64, 0x180000000LL);
COMPARE_BOOL(false, U32, 0, S64, 0x100000000LL);
// F32
COMPARE_BOOL(true, U32, 0, F32, 0);
COMPARE_BOOL(false, U32, 1, F32, 0);
COMPARE_BOOL(false, U32, 0, F32, 1);
COMPARE_BOOL(true, U32, 1, F32, 1);
COMPARE_BOOL(false, U32, 0, F32, -1);
COMPARE_BOOL(false, U32, 1, F32, -1);
COMPARE_BOOL(false, U32, 0xFFFFFFFFU, F32, -1);
COMPARE_BOOL(true, U32, 0x80000000U, F32, 0x80000000);
COMPARE_BOOL(false, U32, 0, F32, 0x80000000);
COMPARE_BOOL(false, U32, 0x80000000U, F32, 0);
COMPARE_BOOL(false, U32, 1, F32, 1.1);
COMPARE_BOOL(false, U32, 0, F32, 0.1);
// F64
COMPARE_BOOL(true, U32, 0, F64, 0);
COMPARE_BOOL(false, U32, 1, F64, 0);
COMPARE_BOOL(false, U32, 0, F64, 1);
COMPARE_BOOL(true, U32, 1, F64, 1);
COMPARE_BOOL(false, U32, 0, F64, -1);
COMPARE_BOOL(false, U32, 1, F64, -1);
COMPARE_BOOL(false, U32, 0xFFFFFFFFU, F64, -1);
COMPARE_BOOL(true, U32, 0x80000000U, F64, 0x80000000);
COMPARE_BOOL(false, U32, 0, F64, 0x80000000);
COMPARE_BOOL(false, U32, 0x80000000U, F64, 0);
COMPARE_BOOL(false, U32, 1, F64, 1.1);
COMPARE_BOOL(false, U32, 0, F64, 0.1);
}
M_TEST_DEFINE(u64Equality) {
struct mScriptValue a;
struct mScriptValue b;
// U32
COMPARE_BOOL(true, U64, 0, U32, 0);
COMPARE_BOOL(false, U64, 0, U32, 1);
COMPARE_BOOL(true, U64, 1, U32, 1);
COMPARE_BOOL(false, U64, 0x080000000ULL, U32, 1);
COMPARE_BOOL(true, U64, 0x080000000ULL, U32, 0x80000000U);
COMPARE_BOOL(false, U64, 0x07FFFFFFFULL, U32, 1);
COMPARE_BOOL(true, U64, 0x07FFFFFFFULL, U32, 0x7FFFFFFFU);
COMPARE_BOOL(false, U64, 0x180000000ULL, U32, 0x80000000U);
COMPARE_BOOL(false, U64, 0x100000000ULL, U32, 0);
// U64
COMPARE_BOOL(true, U64, 0, U64, 0);
COMPARE_BOOL(false, U64, 0, U64, 1);
COMPARE_BOOL(true, U64, 1, U64, 1);
COMPARE_BOOL(false, U64, 0x080000000ULL, U64, 1);
COMPARE_BOOL(true, U64, 0x080000000ULL, U64, 0x080000000ULL);
COMPARE_BOOL(false, U64, 0x07FFFFFFFULL, U64, 1);
COMPARE_BOOL(true, U64, 0x07FFFFFFFULL, U64, 0x07FFFFFFFULL);
COMPARE_BOOL(true, U64, 0x180000000ULL, U64, 0x180000000ULL);
COMPARE_BOOL(true, U64, 0x100000000ULL, U64, 0x100000000ULL);
// S32
COMPARE_BOOL(true, U64, 0, S32, 0);
COMPARE_BOOL(false, U64, 0, S32, 1);
COMPARE_BOOL(true, U64, 1, S32, 1);
COMPARE_BOOL(true, U64, 0x07FFFFFFFULL, S32, 0x7FFFFFFF);
COMPARE_BOOL(false, U64, 0x0FFFFFFFFULL, S32, 0xFFFFFFFF);
COMPARE_BOOL(false, U64, 0x080000000ULL, S32, 0x80000000);
COMPARE_BOOL(false, U64, 0x100000000ULL, S32, 0);
// S64
COMPARE_BOOL(true, U64, 0, S64, 0);
COMPARE_BOOL(false, U64, 0, S64, 1);
COMPARE_BOOL(true, U64, 1, S64, 1);
COMPARE_BOOL(true, U64, 0x07FFFFFFFULL, S64, 0x07FFFFFFFLL);
COMPARE_BOOL(true, U64, 0x0FFFFFFFFULL, S64, 0x0FFFFFFFFLL);
COMPARE_BOOL(true, U64, 0x080000000ULL, S64, 0x080000000LL);
COMPARE_BOOL(false, U64, 0, S64, 0x100000000LL);
COMPARE_BOOL(false, U64, 0x100000000ULL, S64, 0);
COMPARE_BOOL(true, U64, 0x100000000ULL, S64, 0x100000000LL);
COMPARE_BOOL(false, U64, 0x0FFFFFFFFULL, S64, -1);
COMPARE_BOOL(false, U64, 0xFFFFFFFFFFFFFFFFULL, S64, -1);
COMPARE_BOOL(true, U64, 0x080000000ULL, S64, 0x080000000LL);
// F32
COMPARE_BOOL(true, U64, 0, F32, 0);
COMPARE_BOOL(false, U64, 1, F32, 0);
COMPARE_BOOL(false, U64, 0, F32, 1);
COMPARE_BOOL(true, U64, 1, F32, 1);
COMPARE_BOOL(false, U64, 0, F32, -1);
COMPARE_BOOL(false, U64, 1, F32, -1);
COMPARE_BOOL(false, U64, 0xFFFFFFFFFFFFFFFFULL, F32, -1);
COMPARE_BOOL(true, U64, 0x8000000000000000ULL, F32, 0x8000000000000000ULL);
COMPARE_BOOL(false, U64, 0, F32, 0x8000000000000000ULL);
COMPARE_BOOL(false, U64, 0x8000000000000000ULL, F32, 0);
COMPARE_BOOL(false, U64, 1, F32, 1.1);
COMPARE_BOOL(false, U64, 0, F32, 0.1);
// F64
COMPARE_BOOL(true, U64, 0, F64, 0);
COMPARE_BOOL(false, U64, 1, F64, 0);
COMPARE_BOOL(false, U64, 0, F64, 1);
COMPARE_BOOL(true, U64, 1, F64, 1);
COMPARE_BOOL(false, U64, 0, F64, -1);
COMPARE_BOOL(false, U64, 1, F64, -1);
COMPARE_BOOL(false, U64, 0xFFFFFFFFFFFFFFFFULL, F64, -1);
COMPARE_BOOL(true, U64, 0x8000000000000000ULL, F64, 0x8000000000000000ULL);
COMPARE_BOOL(false, U64, 0, F64, 0x8000000000000000ULL);
COMPARE_BOOL(false, U64, 0x8000000000000000ULL, F64, 0);
COMPARE_BOOL(false, U64, 1, F64, 1.1);
COMPARE_BOOL(false, U64, 0, F64, 0.1);
}
M_TEST_DEFINE(f32Equality) {
struct mScriptValue a;
struct mScriptValue b;
// F32
COMPARE_BOOL(true, F32, 0, F32, 0);
COMPARE_BOOL(false, F32, 0, F32, 1);
COMPARE_BOOL(true, F32, 1, F32, 1);
COMPARE_BOOL(true, F32, -1, F32, -1);
COMPARE_BOOL(false, F32, 1.1, F32, 1);
COMPARE_BOOL(false, F32, 1, F32, 1.1);
COMPARE_BOOL(true, F32, 1.1, F32, 1.1);
// F64
COMPARE_BOOL(true, F32, 0, F64, 0);
COMPARE_BOOL(false, F32, 0, F64, 1);
COMPARE_BOOL(true, F32, 1, F64, 1);
COMPARE_BOOL(true, F32, -1, F64, -1);
COMPARE_BOOL(false, F32, 1.1, F64, 1);
COMPARE_BOOL(false, F32, 1, F64, 1.1);
COMPARE_BOOL(true, F32, 1.25, F64, 1.25);
// S32
COMPARE_BOOL(true, F32, 0, S32, 0);
COMPARE_BOOL(false, F32, 0, S32, 1);
COMPARE_BOOL(false, F32, 1, S32, 0);
COMPARE_BOOL(true, F32, 1, S32, 1);
COMPARE_BOOL(false, F32, 1.1, S32, 1);
COMPARE_BOOL(true, F32, -1, S32, -1);
COMPARE_BOOL(false, F32, -1.1, S32, -1);
COMPARE_BOOL(true, F32, 0x40000000, S32, 0x40000000);
COMPARE_BOOL(true, F32, -0x40000000, S32, -0x40000000);
// S64
COMPARE_BOOL(true, F32, 0, S64, 0);
COMPARE_BOOL(false, F32, 0, S64, 1);
COMPARE_BOOL(false, F32, 1, S64, 0);
COMPARE_BOOL(true, F32, 1, S64, 1);
COMPARE_BOOL(false, F32, 1.1, S64, 1);
COMPARE_BOOL(true, F32, -1, S64, -1);
COMPARE_BOOL(false, F32, -1.1, S64, -1);
COMPARE_BOOL(true, F32, 0x040000000LL, S64, 0x040000000LL);
COMPARE_BOOL(true, F32, 0x100000000LL, S64, 0x100000000LL);
COMPARE_BOOL(false, F32, 0x100000000LL, S64, 0);
COMPARE_BOOL(false, F32, 0, S64, 0x100000000LL);
COMPARE_BOOL(true, F32, -0x040000000LL, S64, -0x040000000LL);
// U32
COMPARE_BOOL(true, F32, 0, U32, 0);
COMPARE_BOOL(false, F32, 0, U32, 1);
COMPARE_BOOL(false, F32, 1, U32, 0);
COMPARE_BOOL(true, F32, 1, U32, 1);
COMPARE_BOOL(false, F32, 1.1, U32, 1);
COMPARE_BOOL(true, F32, 0x40000000, U32, 0x40000000);
// U64
COMPARE_BOOL(true, F32, 0, U64, 0);
COMPARE_BOOL(false, F32, 0, U64, 1);
COMPARE_BOOL(false, F32, 1, U64, 0);
COMPARE_BOOL(true, F32, 1, U64, 1);
COMPARE_BOOL(false, F32, 1.1, U64, 1);
COMPARE_BOOL(true, F32, 0x040000000ULL, U64, 0x040000000ULL);
COMPARE_BOOL(true, F32, 0x100000000ULL, U64, 0x100000000ULL);
COMPARE_BOOL(false, F32, 0x100000000ULL, U64, 0);
COMPARE_BOOL(false, F32, 0, U64, 0x100000000ULL);
}
M_TEST_DEFINE(f64Equality) {
struct mScriptValue a;
struct mScriptValue b;
// F32
COMPARE_BOOL(true, F64, 0, F32, 0);
COMPARE_BOOL(false, F64, 0, F32, 1);
COMPARE_BOOL(true, F64, 1, F32, 1);
COMPARE_BOOL(true, F64, -1, F32, -1);
COMPARE_BOOL(false, F64, 1.1, F32, 1);
COMPARE_BOOL(false, F64, 1, F32, 1.1);
COMPARE_BOOL(true, F64, 1.25, F32, 1.25);
// F64
COMPARE_BOOL(true, F64, 0, F64, 0);
COMPARE_BOOL(false, F64, 0, F64, 1);
COMPARE_BOOL(true, F64, 1, F64, 1);
COMPARE_BOOL(true, F64, -1, F64, -1);
COMPARE_BOOL(false, F64, 1.1, F64, 1);
COMPARE_BOOL(false, F64, 1, F64, 1.1);
COMPARE_BOOL(true, F64, 1.1, F64, 1.1);
// S32
COMPARE_BOOL(true, F64, 0, S32, 0);
COMPARE_BOOL(false, F64, 0, S32, 1);
COMPARE_BOOL(false, F64, 1, S32, 0);
COMPARE_BOOL(true, F64, 1, S32, 1);
COMPARE_BOOL(false, F64, 1.1, S32, 1);
COMPARE_BOOL(true, F64, -1, S32, -1);
COMPARE_BOOL(false, F64, -1.1, S32, -1);
COMPARE_BOOL(true, F64, 0x40000000, S32, 0x40000000);
COMPARE_BOOL(true, F64, -0x40000000, S32, -0x40000000);
// S64
COMPARE_BOOL(true, F64, 0, S64, 0);
COMPARE_BOOL(false, F64, 0, S64, 1);
COMPARE_BOOL(false, F64, 1, S64, 0);
COMPARE_BOOL(true, F64, 1, S64, 1);
COMPARE_BOOL(false, F64, 1.1, S64, 1);
COMPARE_BOOL(true, F64, -1, S64, -1);
COMPARE_BOOL(false, F64, -1.1, S64, -1);
COMPARE_BOOL(true, F64, 0x040000000LL, S64, 0x040000000LL);
COMPARE_BOOL(true, F64, 0x100000000LL, S64, 0x100000000LL);
COMPARE_BOOL(false, F64, 0x100000000LL, S64, 0);
COMPARE_BOOL(false, F64, 0, S64, 0x100000000LL);
COMPARE_BOOL(true, F64, -0x040000000LL, S64, -0x040000000LL);
// U32
COMPARE_BOOL(true, F64, 0, U32, 0);
COMPARE_BOOL(false, F64, 0, U32, 1);
COMPARE_BOOL(false, F64, 1, U32, 0);
COMPARE_BOOL(true, F64, 1, U32, 1);
COMPARE_BOOL(false, F64, 1.1, U32, 1);
COMPARE_BOOL(true, F64, 0x40000000, U32, 0x40000000);
// U64
COMPARE_BOOL(true, F64, 0, U64, 0);
COMPARE_BOOL(false, F64, 0, U64, 1);
COMPARE_BOOL(false, F64, 1, U64, 0);
COMPARE_BOOL(true, F64, 1, U64, 1);
COMPARE_BOOL(false, F64, 1.1, U64, 1);
COMPARE_BOOL(true, F64, 0x040000000ULL, U64, 0x040000000ULL);
COMPARE_BOOL(true, F64, 0x100000000ULL, U64, 0x100000000ULL);
COMPARE_BOOL(false, F64, 0x100000000ULL, U64, 0);
COMPARE_BOOL(false, F64, 0, U64, 0x100000000ULL);
}
M_TEST_DEFINE(stringEquality) {
struct mScriptValue* stringA = mScriptStringCreateFromUTF8("hello");
struct mScriptValue* stringB = mScriptStringCreateFromUTF8("world");
struct mScriptValue* stringC = mScriptStringCreateFromUTF8("hello");
struct mScriptValue charpA = mSCRIPT_MAKE_CHARP("hello");
struct mScriptValue charpB = mSCRIPT_MAKE_CHARP("world");
assert_true(stringA->type->equal(stringA, stringC));
assert_false(stringA->type->equal(stringA, stringB));
assert_true(stringA->type->equal(stringA, &charpA));
assert_false(stringA->type->equal(stringA, &charpB));
assert_true(charpA.type->equal(&charpA, stringA));
assert_false(charpA.type->equal(&charpA, stringB));
charpB = mSCRIPT_MAKE_CHARP("hello");
assert_true(charpA.type->equal(&charpA, &charpB));
charpB = mSCRIPT_MAKE_CHARP("world");
assert_false(charpA.type->equal(&charpA, &charpB));
mScriptValueDeref(stringA);
mScriptValueDeref(stringB);
mScriptValueDeref(stringC);
}
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 = mSCRIPT_MAKE_S32(1234);
struct mScriptValue badKey = mSCRIPT_MAKE_S32(1235);
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_DEFINE(hashTableString) {
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 key = mSCRIPT_MAKE_CHARP("key");
struct mScriptValue badKey = mSCRIPT_MAKE_CHARP("bad");
assert_true(mScriptTableInsert(table, &key, intValue));
assert_int_equal(intValue->refs, 2);
struct mScriptValue* lookupValue = mScriptTableLookup(table, &key);
assert_non_null(lookupValue);
assert_ptr_equal(lookupValue, intValue);
lookupValue = mScriptTableLookup(table, &badKey);
assert_null(lookupValue);
assert_true(mScriptTableRemove(table, &key));
assert_int_equal(intValue->refs, 1);
mScriptValueDeref(intValue);
mScriptValueDeref(table);
}
M_TEST_DEFINE(stringIsHello) {
struct mScriptFrame frame;
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CHARP, "hello");
assert_true(mScriptInvoke(&boundIsHello, &frame));
int val;
assert_true(mScriptPopS32(&frame.returnValues, &val));
assert_int_equal(val, 1);
mScriptFrameDeinit(&frame);
}
M_TEST_DEFINE(stringIsNotHello) {
struct mScriptFrame frame;
mScriptFrameInit(&frame);
mSCRIPT_PUSH(&frame.arguments, CHARP, "world");
assert_true(mScriptInvoke(&boundIsHello, &frame));
int val;
assert_true(mScriptPopS32(&frame.returnValues, &val));
assert_int_equal(val, 0);
mScriptFrameDeinit(&frame);
}
M_TEST_SUITE_DEFINE(mScript,
cmocka_unit_test(voidArgs),
cmocka_unit_test(voidFunc),
cmocka_unit_test(identityFunctionS32),
cmocka_unit_test(identityFunctionS64),
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(wrongPopType),
cmocka_unit_test(wrongPopSize),
cmocka_unit_test(wrongConst),
cmocka_unit_test(coerceToFloat),
cmocka_unit_test(coerceFromFloat),
cmocka_unit_test(coerceNarrow),
cmocka_unit_test(coerceWiden),
cmocka_unit_test(s32Equality),
cmocka_unit_test(s64Equality),
cmocka_unit_test(u32Equality),
cmocka_unit_test(u64Equality),
cmocka_unit_test(f32Equality),
cmocka_unit_test(f64Equality),
cmocka_unit_test(stringEquality),
cmocka_unit_test(hashTableBasic),
cmocka_unit_test(hashTableString),
cmocka_unit_test(stringIsHello),
cmocka_unit_test(stringIsNotHello))

1401
src/script/types.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -161,6 +161,24 @@ void* TableLookup(const struct Table* table, uint32_t key) {
void TableInsert(struct Table* table, uint32_t key, void* value) {
struct TableList* list = _getList(table, key);
if (table->size >= table->tableSize * REBALANCE_THRESHOLD) {
struct Table newTable;
TableInit(&newTable, table->tableSize * REBALANCE_THRESHOLD, NULL);
memcpy(&newTable.fn, &table->fn, sizeof(newTable.fn));
size_t i;
for (i = 0; i < table->tableSize; ++i) {
struct TableList* list = &table->table[i];
size_t j;
for (j = 0; j < list->nEntries; ++j) {
TableInsert(&newTable, list->list[j].key, list->list[j].value);
}
free(list->list);
}
free(table->table);
table->tableSize = newTable.tableSize;
table->table = newTable.table;
list = _getList(table, key);
}
TABLE_LOOKUP_START(TABLE_COMPARATOR, list) {
if (value != lookupResult->value) {
if (table->fn.deinitializer) {
@ -525,6 +543,17 @@ void HashTableEnumerateBinary(const struct Table* table, void (*handler)(const c
}
}
void HashTableEnumerateCustom(const struct Table* table, void (*handler)(void* key, void* value, void* user), void* user) {
size_t i;
for (i = 0; i < table->tableSize; ++i) {
const struct TableList* list = &table->table[i];
size_t j;
for (j = 0; j < list->nEntries; ++j) {
handler((char*) list->list[j].stringKey, list->list[j].value, user);
}
}
}
const char* HashTableSearch(const struct Table* table, bool (*predicate)(const char* key, const void* value, const void* user), const void* user) {
size_t i;
const char* result = NULL;