Scripting: Add skeleton of storage API

This commit is contained in:
Vicki Pfau 2023-02-04 01:18:01 -08:00
parent aefcd174a8
commit 00a34e0d07
4 changed files with 354 additions and 0 deletions

View File

@ -0,0 +1,21 @@
/* Copyright (c) 2013-2023 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_STORAGE_H
#define M_SCRIPT_STORAGE_H
#include <mgba-util/common.h>
CXX_GUARD_START
#include <mgba/script/context.h>
#include <mgba/script/macros.h>
struct VFile;
void mScriptContextAttachStorage(struct mScriptContext* context);
CXX_GUARD_END
#endif

View File

@ -4,12 +4,17 @@ set(SOURCE_FILES
input.c
socket.c
stdlib.c
storage.c
types.c)
set(TEST_FILES
test/classes.c
test/types.c)
if(USE_JSON_C)
list(APPEND SOURCE_FILES storage.c)
endif()
if(USE_LUA)
list(APPEND SOURCE_FILES engines/lua.c)
list(APPEND TEST_FILES
@ -17,6 +22,10 @@ if(USE_LUA)
test/input.c
test/lua.c
test/stdlib.c)
if(USE_JSON_C)
list(APPEND TEST_FILES test/storage.c)
endif()
endif()
source_group("Scripting" FILES ${SOURCE_FILES})

155
src/script/storage.c Normal file
View File

@ -0,0 +1,155 @@
/* Copyright (c) 2013-2023 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/storage.h>
#define STORAGE_LEN_MAX 64
struct mScriptStorageBucket {
char* name;
struct mScriptValue* root;
bool dirty;
};
struct mScriptStorageContext {
struct Table buckets;
};
void mScriptStorageBucketDeinit(void*);
struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* adapter, const char* key);
static void mScriptStorageBucketSet(struct mScriptStorageBucket* adapter, const char* key, struct mScriptValue* value);
static void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* adapter, const char* key, struct mScriptValue* value);
static void mScriptStorageBucketSetSInt(struct mScriptStorageBucket* adapter, const char* key, int64_t value);
static void mScriptStorageBucketSetUInt(struct mScriptStorageBucket* adapter, const char* key, uint64_t value);
static void mScriptStorageBucketSetFloat(struct mScriptStorageBucket* adapter, const char* key, double value);
static void mScriptStorageBucketSetBool(struct mScriptStorageBucket* adapter, const char* key, bool value);
void mScriptStorageContextDeinit(struct mScriptStorageContext*);
struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext*, const char* name);
mSCRIPT_DECLARE_STRUCT(mScriptStorageBucket);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageBucket, WRAPPER, _get, mScriptStorageBucketGet, 1, CHARP, key);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setSInt, mScriptStorageBucketSetSInt, 2, CHARP, key, S64, value);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setUInt, mScriptStorageBucketSetUInt, 2, CHARP, key, U64, value);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setFloat, mScriptStorageBucketSetFloat, 2, CHARP, key, F64, value);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setBool, mScriptStorageBucketSetBool, 2, CHARP, key, BOOL, value);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setStr, mScriptStorageBucketSet, 2, CHARP, key, WSTR, value);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setList, mScriptStorageBucketSet, 2, CHARP, key, WLIST, value);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setTable, mScriptStorageBucketSet, 2, CHARP, key, WTABLE, value);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageBucket, setVoid, mScriptStorageBucketSetVoid, 2, CHARP, key, NUL, value);
mSCRIPT_DEFINE_STRUCT(mScriptStorageBucket)
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setSInt)
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setUInt)
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setFloat)
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setBool)
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setStr)
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setList)
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setTable)
mSCRIPT_DEFINE_STRUCT_DEFAULT_SET(mScriptStorageBucket, setVoid)
mSCRIPT_DEFINE_STRUCT_DEFAULT_GET(mScriptStorageBucket)
mSCRIPT_DEFINE_END;
mSCRIPT_DECLARE_STRUCT(mScriptStorageContext);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptStorageContext, _deinit, mScriptStorageContextDeinit, 0);
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptStorageContext, S(mScriptStorageBucket), getBucket, mScriptStorageGetBucket, 1, CHARP, key);
mSCRIPT_DEFINE_STRUCT(mScriptStorageContext)
mSCRIPT_DEFINE_STRUCT_DEINIT(mScriptStorageContext)
mSCRIPT_DEFINE_STRUCT_METHOD(mScriptStorageContext, getBucket)
mSCRIPT_DEFINE_END;
struct mScriptValue* mScriptStorageBucketGet(struct mScriptStorageBucket* bucket, const char* key) {
struct mScriptValue* val = mScriptTableLookup(bucket->root, &mSCRIPT_MAKE_CHARP(key));
if (val) {
mScriptValueRef(val);
}
return val;
}
void mScriptStorageBucketSet(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value) {
struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key);
if (value->type->base == mSCRIPT_TYPE_WRAPPER) {
value = mScriptValueUnwrap(value);
}
mScriptTableInsert(bucket->root, vkey, value);
mScriptValueDeref(vkey);
bucket->dirty = true;
}
void mScriptStorageBucketSetVoid(struct mScriptStorageBucket* bucket, const char* key, struct mScriptValue* value) {
UNUSED(value);
struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key);
mScriptTableInsert(bucket->root, vkey, &mScriptValueNull);
mScriptValueDeref(vkey);
bucket->dirty = true;
}
#define MAKE_SCALAR_SETTER(NAME, TYPE) \
void mScriptStorageBucketSet ## NAME (struct mScriptStorageBucket* bucket, const char* key, mSCRIPT_TYPE_C_ ## TYPE value) { \
struct mScriptValue* vkey = mScriptStringCreateFromUTF8(key); \
struct mScriptValue* vval = mScriptValueAlloc(mSCRIPT_TYPE_MS_ ## TYPE); \
vval->value.mSCRIPT_TYPE_FIELD_ ## TYPE = value; \
mScriptTableInsert(bucket->root, vkey, vval); \
mScriptValueDeref(vkey); \
mScriptValueDeref(vval); \
bucket->dirty = true; \
}
MAKE_SCALAR_SETTER(SInt, S64)
MAKE_SCALAR_SETTER(UInt, U64)
MAKE_SCALAR_SETTER(Float, F64)
MAKE_SCALAR_SETTER(Bool, BOOL)
void mScriptContextAttachStorage(struct mScriptContext* context) {
struct mScriptStorageContext* storage = calloc(1, sizeof(*storage));
struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S(mScriptStorageContext));
value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
value->value.opaque = storage;
HashTableInit(&storage->buckets, 0, mScriptStorageBucketDeinit);
mScriptContextSetGlobal(context, "storage", value);
mScriptContextSetDocstring(context, "storage", "Singleton instance of struct::mScriptStorageContext");
}
void mScriptStorageContextDeinit(struct mScriptStorageContext* storage) {
HashTableDeinit(&storage->buckets);
}
struct mScriptStorageBucket* mScriptStorageGetBucket(struct mScriptStorageContext* storage, const char* name) {
if (!name) {
return NULL;
}
// Check if name is allowed
// Currently only names matching /[0-9A-Za-z_.]+/ are allowed
size_t i;
for (i = 0; name[i]; ++i) {
if (i >= STORAGE_LEN_MAX) {
return NULL;
}
if (!isalnum(name[i]) && name[i] != '_' && name[i] != '.') {
return NULL;
}
}
struct mScriptStorageBucket* bucket = HashTableLookup(&storage->buckets, name);
if (bucket) {
return bucket;
}
bucket = calloc(1, sizeof(*bucket));
bucket->root = mScriptValueAlloc(mSCRIPT_TYPE_MS_TABLE);
bucket->name = strdup(name);
HashTableInsert(&storage->buckets, name, bucket);
return bucket;
}
void mScriptStorageBucketDeinit(void* data) {
struct mScriptStorageBucket* bucket = data;
mScriptValueDeref(bucket->root);
free(bucket->name);
free(bucket);
}

169
src/script/test/storage.c Normal file
View File

@ -0,0 +1,169 @@
/* Copyright (c) 2013-2023 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/storage.h>
#include <mgba/script/types.h>
#include "script/test.h"
#define SETUP_LUA \
struct mScriptContext context; \
mScriptContextInit(&context); \
struct mScriptEngineContext* lua = mScriptContextRegisterEngine(&context, mSCRIPT_ENGINE_LUA); \
mScriptContextAttachStdlib(&context); \
mScriptContextAttachStorage(&context)
M_TEST_SUITE_SETUP(mScriptStorage) {
if (mSCRIPT_ENGINE_LUA->init) {
mSCRIPT_ENGINE_LUA->init(mSCRIPT_ENGINE_LUA);
}
return 0;
}
M_TEST_SUITE_TEARDOWN(mScriptStorage) {
if (mSCRIPT_ENGINE_LUA->deinit) {
mSCRIPT_ENGINE_LUA->deinit(mSCRIPT_ENGINE_LUA);
}
return 0;
}
M_TEST_DEFINE(basicInt) {
SETUP_LUA;
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
TEST_PROGRAM("assert(bucket)");
TEST_PROGRAM("assert(not bucket.a)");
TEST_PROGRAM("bucket.a = 1");
TEST_PROGRAM("assert(bucket.a == 1)");
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(basicFloat) {
SETUP_LUA;
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
TEST_PROGRAM("assert(bucket)");
TEST_PROGRAM("assert(not bucket.a)");
TEST_PROGRAM("bucket.a = 0.5");
TEST_PROGRAM("assert(bucket.a == 0.5)");
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(basicBool) {
SETUP_LUA;
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
TEST_PROGRAM("assert(bucket)");
TEST_PROGRAM("assert(not bucket.a)");
TEST_PROGRAM("bucket.a = true");
TEST_PROGRAM("assert(bucket.a == true)");
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(basicNil) {
SETUP_LUA;
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
TEST_PROGRAM("assert(bucket)");
TEST_PROGRAM("assert(not bucket.a)");
TEST_PROGRAM("bucket.a = nil");
TEST_PROGRAM("assert(bucket.a == nil)");
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(basicString) {
SETUP_LUA;
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
TEST_PROGRAM("assert(bucket)");
TEST_PROGRAM("assert(not bucket.a)");
TEST_PROGRAM("bucket.a = 'hello'");
TEST_PROGRAM("assert(bucket.a == 'hello')");
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(basicList) {
SETUP_LUA;
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
TEST_PROGRAM("assert(bucket)");
TEST_PROGRAM("assert(not bucket.a)");
TEST_PROGRAM("bucket.a = {1}");
TEST_PROGRAM("assert(#bucket.a == 1)");
TEST_PROGRAM("assert(bucket.a[1] == 1)");
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(basicTable) {
SETUP_LUA;
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
TEST_PROGRAM("assert(bucket)");
TEST_PROGRAM("assert(not bucket.a)");
TEST_PROGRAM("bucket.a = {['a']=1}");
TEST_PROGRAM("assert(#bucket.a == 1)");
TEST_PROGRAM("assert(bucket.a.a == 1)");
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(nullByteString) {
SETUP_LUA;
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
TEST_PROGRAM("assert(bucket)");
TEST_PROGRAM("assert(not bucket.a)");
TEST_PROGRAM("bucket.a = 'a\\x00b'");
TEST_PROGRAM("assert(bucket.a == 'a\\x00b')");
TEST_PROGRAM("assert(#bucket.a == 3)");
mScriptContextDeinit(&context);
}
M_TEST_DEFINE(structured) {
SETUP_LUA;
TEST_PROGRAM("bucket = storage:getBucket('xtest')");
TEST_PROGRAM("assert(bucket)");
TEST_PROGRAM("assert(not bucket.a)");
TEST_PROGRAM(
"bucket.a = {\n"
" ['a'] = 1,\n"
" ['b'] = {1},\n"
" ['c'] = {\n"
" ['d'] = 1\n"
" }\n"
"}"
);
TEST_PROGRAM("assert(bucket.a)");
TEST_PROGRAM("assert(bucket.a.a == 1)");
TEST_PROGRAM("assert(#bucket.a.b == 1)");
TEST_PROGRAM("assert(bucket.a.b[1] == 1)");
TEST_PROGRAM("assert(#bucket.a.c == 1)");
TEST_PROGRAM("assert(bucket.a.c.d == 1)");
mScriptContextDeinit(&context);
}
M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(mScriptStorage,
cmocka_unit_test(basicInt),
cmocka_unit_test(basicFloat),
cmocka_unit_test(basicBool),
cmocka_unit_test(basicNil),
cmocka_unit_test(basicString),
cmocka_unit_test(basicList),
cmocka_unit_test(basicTable),
cmocka_unit_test(nullByteString),
cmocka_unit_test(structured),
)