diff --git a/include/mgba/core/log.h b/include/mgba/core/log.h index e2ac55e4b..d6217aca1 100644 --- a/include/mgba/core/log.h +++ b/include/mgba/core/log.h @@ -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; diff --git a/include/mgba/core/scripting.h b/include/mgba/core/scripting.h index c65897e17..fbb560237 100644 --- a/include/mgba/core/scripting.h +++ b/include/mgba/core/scripting.h @@ -10,13 +10,18 @@ CXX_GUARD_START +#include #ifdef USE_DEBUGGERS #include #endif #include +mLOG_DECLARE_CATEGORY(SCRIPT); + struct mCore; +struct mLogger; mSCRIPT_DECLARE_STRUCT(mCore); +mSCRIPT_DECLARE_STRUCT(mLogger); struct mScriptBridge; struct VFile; @@ -52,10 +57,12 @@ bool mScriptBridgeLoadScript(struct mScriptBridge*, const char* name); bool mScriptBridgeLookupSymbol(struct mScriptBridge*, const char* name, int32_t* out); struct mScriptContext; -struct mCore; void mScriptContextAttachCore(struct mScriptContext*, struct mCore*); void mScriptContextDetachCore(struct mScriptContext*); +void mScriptContextAttachLogger(struct mScriptContext*, struct mLogger*); +void mScriptContextDetachLogger(struct mScriptContext*); + CXX_GUARD_END #endif diff --git a/src/core/log.c b/src/core/log.c index e0f5a002a..4612c02b9 100644 --- a/src/core/log.c +++ b/src/core/log.c @@ -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); diff --git a/src/core/scripting.c b/src/core/scripting.c index 8adc5f55d..9b4f4534e 100644 --- a/src/core/scripting.c +++ b/src/core/scripting.c @@ -10,6 +10,8 @@ #include #include +mLOG_DEFINE_CATEGORY(SCRIPT, "Scripting", "script"); + struct mScriptBridge { struct Table engines; struct mDebugger* debugger; @@ -196,3 +198,36 @@ void mScriptContextAttachCore(struct mScriptContext* context, struct mCore* core void mScriptContextDetachCore(struct mScriptContext* context) { mScriptContextRemoveGlobal(context, "emu"); } + +void mScriptLog(struct mLogger* logger, struct mScriptString* msg) { + mLogExplicit(logger, _mLOG_CAT_SCRIPT, mLOG_INFO, "%s", msg->buffer); +} + +void mScriptWarn(struct mLogger* logger, struct mScriptString* msg) { + mLogExplicit(logger, _mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg->buffer); +} + +void mScriptError(struct mLogger* logger, struct mScriptString* msg) { + mLogExplicit(logger, _mLOG_CAT_SCRIPT, mLOG_ERROR, "%s", msg->buffer); +} + +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mLogger, log, mScriptLog, 1, STR, msg); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mLogger, warn, mScriptWarn, 1, STR, msg); +mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mLogger, error, mScriptError, 1, STR, msg); + +mSCRIPT_DEFINE_STRUCT(mLogger) +mSCRIPT_DEFINE_STRUCT_METHOD(mLogger, log) +mSCRIPT_DEFINE_STRUCT_METHOD(mLogger, warn) +mSCRIPT_DEFINE_STRUCT_METHOD(mLogger, error) +mSCRIPT_DEFINE_END; + +void mScriptContextAttachLogger(struct mScriptContext* context, struct mLogger* logger) { + struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mLogger)); + value->value.opaque = logger; + mScriptContextSetGlobal(context, "console", value); + mScriptValueDeref(value); +} + +void mScriptContextDetachLogger(struct mScriptContext* context) { + mScriptContextRemoveGlobal(context, "console"); +} diff --git a/src/core/test/scripting.c b/src/core/test/scripting.c index 63d2ef61e..0f5e55f7f 100644 --- a/src/core/test/scripting.c +++ b/src/core/test/scripting.c @@ -6,6 +6,7 @@ #include "util/test/suite.h" #include +#include #include #include #include @@ -23,6 +24,13 @@ #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 @@ -76,6 +84,59 @@ static const uint8_t _fakeGBROM[0x4000] = { 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); @@ -230,6 +291,45 @@ M_TEST_DEFINE(memoryWrite) { 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); + + LOAD_PROGRAM( + "assert(not console)\n" + ); + assert_true(lua->run(lua)); + + LOAD_PROGRAM( + "a:log(\"l2\")\n" + ); + assert_false(lua->run(lua)); + + mScriptTestLoggerDeinit(&logger); + mScriptContextDeinit(&context); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore, cmocka_unit_test(globals), cmocka_unit_test(infoFuncs), @@ -237,4 +337,5 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptCore, cmocka_unit_test(runFrame), cmocka_unit_test(memoryRead), cmocka_unit_test(memoryWrite), + cmocka_unit_test(logging), )