diff --git a/include/mgba/script/types.h b/include/mgba/script/types.h index 37777abe4..f2e9a37a9 100644 --- a/include/mgba/script/types.h +++ b/include/mgba/script/types.h @@ -202,6 +202,7 @@ CXX_GUARD_START .alloc = NULL, \ .free = NULL, \ .cast = _mSTStructCast_ ## STRUCT, \ + .constType = &mSTStructConst_ ## STRUCT, \ }; \ const struct mScriptType mSTStructConst_ ## STRUCT = { \ .base = mSCRIPT_TYPE_OBJECT, \ @@ -255,6 +256,13 @@ CXX_GUARD_START }, \ }, +#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, ...) \ _mDEFER(_mDEFER(_mCAT(mSCRIPT_POP_, _mSUCC ## NPARAMS)) (&frame->arguments, _mCOMMA_ ## NPARAMS(S(TYPE), __VA_ARGS__))); \ if (mScriptListSize(&frame->arguments)) { \ @@ -493,7 +501,7 @@ struct mScriptClassInitDetails { enum mScriptClassInitType type; union { const char* comment; - const struct mScriptTypeClass* parent; + const struct mScriptType* parent; struct mScriptClassMember member; } info; }; @@ -501,6 +509,7 @@ struct mScriptClassInitDetails { struct mScriptTypeClass { bool init; const struct mScriptClassInitDetails* details; + const struct mScriptType* parent; struct Table staticMembers; struct Table instanceMembers; }; @@ -518,6 +527,7 @@ struct mScriptType { struct mScriptTypeClass* cls; void* opaque; } details; + const struct mScriptType* constType; void (*alloc)(struct mScriptValue*); void (*free)(struct mScriptValue*); uint32_t (*hash)(const struct mScriptValue*); diff --git a/src/script/test/classes.c b/src/script/test/classes.c index a08da753b..272bc5fc0 100644 --- a/src/script/test/classes.c +++ b/src/script/test/classes.c @@ -21,6 +21,20 @@ 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; +}; + static int32_t testAi0(struct TestA* a) { return a->i; } @@ -87,7 +101,24 @@ mSCRIPT_DEFINE_STRUCT(TestA) mSCRIPT_DEFINE_STATIC_MEMBER(S16, s_hUnaligned) mSCRIPT_DEFINE_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_EXPORT_STRUCT(TestA); +mSCRIPT_EXPORT_STRUCT(TestB); +mSCRIPT_EXPORT_STRUCT(TestC); +mSCRIPT_EXPORT_STRUCT(TestD); M_TEST_DEFINE(testALayout) { struct mScriptTypeClass* cls = mSCRIPT_TYPE_MS_S(TestA)->details.cls; @@ -492,9 +523,331 @@ M_TEST_DEFINE(testADynamic) { 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; + + // Instance members + 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; + + // Instance members + 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_SUITE_DEFINE(mScriptClasses, cmocka_unit_test(testALayout), cmocka_unit_test(testAGet), cmocka_unit_test(testASet), cmocka_unit_test(testAStatic), - cmocka_unit_test(testADynamic)) + 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)) diff --git a/src/script/types.c b/src/script/types.c index fd179c91f..66423761a 100644 --- a/src/script/types.c +++ b/src/script/types.c @@ -741,33 +741,34 @@ void mScriptFrameDeinit(struct mScriptFrame* frame) { mScriptListDeinit(&frame->arguments); } -void mScriptClassInit(struct mScriptTypeClass* cls) { - if (cls->init) { - return; - } - HashTableInit(&cls->staticMembers, 0, free); - HashTableInit(&cls->instanceMembers, 0, free); - size_t staticOffset = 0; +static void _mScriptClassInit(struct mScriptTypeClass* cls, const struct mScriptClassInitDetails* details, bool child) { const char* docstring = NULL; + size_t staticOffset = 0; size_t i; - for (i = 0; cls->details[i].type != mSCRIPT_CLASS_INIT_END; ++i) { - const struct mScriptClassInitDetails* details = &cls->details[i]; + for (i = 0; details[i].type != mSCRIPT_CLASS_INIT_END; ++i) { + const struct mScriptClassInitDetails* detail = &details[i]; struct mScriptClassMember* member; - switch (details->type) { + switch (detail->type) { case mSCRIPT_CLASS_INIT_END: break; case mSCRIPT_CLASS_INIT_DOCSTRING: - docstring = details->info.comment; + docstring = detail->info.comment; break; case mSCRIPT_CLASS_INIT_INHERIT: - // TODO - abort(); + member = calloc(1, sizeof(*member)); + member->name = "_super"; + member->type = detail->info.parent; + if (!child) { + cls->parent = detail->info.parent; + } + HashTableInsert(&cls->instanceMembers, member->name, member); + _mScriptClassInit(cls, detail->info.parent->details.cls->details, true); break; case mSCRIPT_CLASS_INIT_INSTANCE_MEMBER: member = calloc(1, sizeof(*member)); - memcpy(member, &details->info.member, sizeof(*member)); + memcpy(member, &detail->info.member, sizeof(*member)); if (docstring) { member->docstring = docstring; docstring = NULL; @@ -775,27 +776,40 @@ void mScriptClassInit(struct mScriptTypeClass* cls) { HashTableInsert(&cls->instanceMembers, member->name, member); break; case mSCRIPT_CLASS_INIT_STATIC_MEMBER: - member = calloc(1, sizeof(*member)); - memcpy(member, &details->info.member, sizeof(*member)); - if (docstring) { - member->docstring = docstring; - docstring = NULL; - } - - // Alignment check - if (staticOffset & (details->info.member.type->size - 1)) { - size_t size = details->info.member.type->size; - if (size > MAX_ALIGNMENT) { - size = MAX_ALIGNMENT; + if (!child) { + member = calloc(1, sizeof(*member)); + memcpy(member, &detail->info.member, sizeof(*member)); + if (docstring) { + member->docstring = docstring; + docstring = NULL; } - staticOffset = (staticOffset & ~(size - 1)) + size; + + // Alignment check + if (staticOffset & (detail->info.member.type->size - 1)) { + size_t size = detail->info.member.type->size; + if (size > MAX_ALIGNMENT) { + size = MAX_ALIGNMENT; + } + staticOffset = (staticOffset & ~(size - 1)) + size; + } + member->offset = staticOffset; + staticOffset += detail->info.member.type->size; + HashTableInsert(&cls->staticMembers, member->name, member); } - member->offset = staticOffset; - staticOffset += details->info.member.type->size; - HashTableInsert(&cls->staticMembers, member->name, member); break; } } +} + +void mScriptClassInit(struct mScriptTypeClass* cls) { + if (cls->init) { + return; + } + HashTableInit(&cls->staticMembers, 0, free); + HashTableInit(&cls->instanceMembers, 0, free); + + _mScriptClassInit(cls, cls->details, false); + cls->init = true; } @@ -880,6 +894,15 @@ bool mScriptObjectGet(struct mScriptValue* obj, const char* member, struct mScri val->type = m->type; m->type->alloc(val); break; + case mSCRIPT_TYPE_OBJECT: + val->refs = mSCRIPT_VALUE_UNREF; + val->value.opaque = rawMember; + if (obj->type->isConst && !m->type->isConst) { + val->type = m->type->constType; + } else { + val->type = m->type; + } + break; default: return false; }