Skip to content

Commit

Permalink
Added dukglue_register_method_varargs().
Browse files Browse the repository at this point in the history
Works just like dukglue_register_method, but the method must have the form
of a Duktape C function (`duk_ret_t method(duk_context*)`). The method is
responsible for pulling any argument values it wants off the stack. This
can be used to write functions that take a variable number of arguments.

Fixed some missing duk_pop()s in register_delete and register_property
(embarrassing that those have been around this long...).
  • Loading branch information
Aloshi committed Jan 7, 2017
1 parent 88b5bca commit 2172fa5
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 0 deletions.
44 changes: 44 additions & 0 deletions include/dukglue/detail_method.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ namespace dukglue
return DUK_RET_TYPE_ERROR;
}

duk_pop_2(ctx);

// (should always be valid unless someone is intentionally messing with this.obj_ptr...)
Cls* obj = static_cast<Cls*>(obj_void);
Expand Down Expand Up @@ -133,5 +134,48 @@ namespace dukglue
}
};
};

template <bool isConst, typename Cls>
struct MethodVariadicRuntime
{
typedef MethodInfo<isConst, Cls, duk_ret_t, duk_context*> MethodInfo;
typedef typename MethodInfo::MethodHolder MethodHolder;

static duk_ret_t finalize_method(duk_context* ctx)
{
return MethodInfo::MethodRuntime::finalize_method(ctx);
}

static duk_ret_t call_native_method(duk_context* ctx)
{
// get this.obj_ptr
duk_push_this(ctx);
duk_get_prop_string(ctx, -1, "\xFF" "obj_ptr");
void* obj_void = duk_get_pointer(ctx, -1);
if (obj_void == nullptr) {
duk_error(ctx, DUK_RET_REFERENCE_ERROR, "Invalid native object for 'this'");
return DUK_RET_REFERENCE_ERROR;
}

duk_pop_2(ctx); // pop this.obj_ptr and this

// get current_function.method_info
duk_push_current_function(ctx);
duk_get_prop_string(ctx, -1, "\xFF" "method_holder");
void* method_holder_void = duk_require_pointer(ctx, -1);
if (method_holder_void == nullptr) {
duk_error(ctx, DUK_RET_TYPE_ERROR, "Method pointer missing?!");
return DUK_RET_TYPE_ERROR;
}

duk_pop_2(ctx);

// (should always be valid unless someone is intentionally messing with this.obj_ptr...)
Cls* obj = static_cast<Cls*>(obj_void);
MethodHolder* method_holder = static_cast<MethodHolder*>(method_holder_void);

return (*obj.*method_holder->method)(ctx);
}
};
}
}
40 changes: 40 additions & 0 deletions include/dukglue/register_class.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,45 @@ void dukglue_register_method(duk_context* ctx, typename std::conditional<isConst
duk_pop(ctx); // pop prototype
}

// methods with a variable number of (script) arguments
template<class Cls>
inline void dukglue_register_method_varargs(duk_context* ctx, duk_ret_t(Cls::*method)(duk_context*), const char* name)
{
dukglue_register_method_varargs<false, Cls>(ctx, method, name);
}

template<class Cls>
inline void dukglue_register_method_varargs(duk_context* ctx, duk_ret_t(Cls::*method)(duk_context*) const, const char* name)
{
dukglue_register_method_varargs<true, Cls>(ctx, method, name);
}

template<bool isConst, typename Cls>
void dukglue_register_method_varargs(duk_context* ctx,
typename std::conditional<isConst, duk_ret_t(Cls::*)(duk_context*) const, duk_ret_t(Cls::*)(duk_context*)>::type method,
const char* name)
{
using namespace dukglue::detail;
typedef MethodVariadicRuntime<isConst, Cls> MethodVariadicInfo;

duk_c_function method_func = MethodVariadicInfo::call_native_method;

ProtoManager::push_prototype<Cls>(ctx);

duk_push_c_function(ctx, method_func, DUK_VARARGS);

duk_push_pointer(ctx, new typename MethodVariadicInfo::MethodHolder{ method });
duk_put_prop_string(ctx, -2, "\xFF" "method_holder"); // consumes raw method pointer

// make sure we free the method_holder when this function is removed
duk_push_c_function(ctx, MethodVariadicInfo::finalize_method, 1);
duk_set_finalizer(ctx, -2);

duk_put_prop_string(ctx, -2, name); // consumes method function

duk_pop(ctx); // pop prototype
}

inline void dukglue_invalidate_object(duk_context* ctx, void* obj_ptr)
{
dukglue::detail::RefManager::find_and_invalidate_native_object(ctx, obj_ptr);
Expand All @@ -132,4 +171,5 @@ void dukglue_register_delete(duk_context* ctx)
dukglue::detail::ProtoManager::push_prototype<Cls>(ctx);
duk_push_c_function(ctx, delete_func, 0);
duk_put_prop_string(ctx, -2, "delete");
duk_pop(ctx); // pop prototype
}
1 change: 1 addition & 0 deletions include/dukglue/register_property.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,5 @@ void dukglue_register_property(duk_context* ctx,
| DUK_DEFPROP_FORCE /* allow overriding built-ins and previously defined properties */;

duk_def_prop(ctx, -4, flags);
duk_pop(ctx); // pop prototype
}
12 changes: 12 additions & 0 deletions tests/test_classes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,46 +64,58 @@ void test_classes() {

// - test that script can actually call constructor + methods
test_eval(ctx, "var test = new Dog('Gus');");
duk_pop(ctx);
test_eval_expect(ctx, "test.getName();", "Gus");
test_eval(ctx, "test.rename('Archie');");
duk_pop(ctx);
test_eval_expect(ctx, "test.getName();", "Archie");

// - test native objects as arguments to native functions
dukglue_register_function(ctx, pokeWithStick, "pokeWithStick");
test_eval(ctx, "pokeWithStick(test);");
duk_pop(ctx);
test_eval_expect(ctx, "test.getBarkCount();", 1);

// - test type-safety when passing native types
dukglue_register_constructor<Goat>(ctx, "Goat");
dukglue_register_delete<Goat>(ctx);
test_eval(ctx, "var goat = new Goat();");
duk_pop(ctx);
test_eval_expect_error(ctx, "pokeWithStick(goat);");
test_eval(ctx, "goat.delete()");
duk_pop(ctx);

// - test that objects can be passed from C++ to script correctly
dukglue_register_function(ctx, visitPuppy, "visitPuppy");
test_eval(ctx, "var puppy = visitPuppy();");
duk_pop(ctx);
test_eval_expect(ctx, "puppy.getName()", "Sammie");
test_eval(ctx, "pokeWithStick(puppy);");
duk_pop(ctx);
test_eval_expect(ctx, "puppy.getBarkCount()", 1);
test_assert(visitPuppy()->getBarkCount() == 1);

// - test that dynamic fields persist when passing the same native object twice
test_eval(ctx, "puppy.dynamicfield = 4;");
duk_pop(ctx);
test_eval_expect(ctx, "puppy.dynamicfield", 4);
test_eval(ctx, "puppy = null; puppy = visitPuppy();");
duk_pop(ctx);
test_eval_expect(ctx, "puppy.dynamicfield", 4);

// - test that references are properly invalidated by obj.delete()
dukglue_register_delete<Dog>(ctx);
test_eval(ctx, "var myPuppy = new Dog('Squirt');");
test_eval(ctx, "pokeWithStick(myPuppy);");
test_eval(ctx, "myPuppy.delete();");
duk_pop_3(ctx);
test_eval_expect_error(ctx, "myPuppy.bark();");
test_eval_expect_error(ctx, "pokeWithStick(myPuppy);");
test_eval_expect_error(ctx, "myPuppy.delete();");
test_eval(ctx, "test.delete()");
duk_pop(ctx);

test_assert(duk_get_top(ctx) == 0);
duk_destroy_heap(ctx);

std::cout << "Classes tested OK" << std::endl;
Expand Down
7 changes: 7 additions & 0 deletions tests/test_inheritance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@ void test_inheritance() {
dukglue_set_base_class<Shape, Rectangle>(ctx);

test_eval(ctx, "var circ = new Circle(2, 3, 4);");
duk_pop(ctx);
test_eval_expect(ctx, "circ.describe()", "A lazily-drawn circle, drawn at 2, 3, with a radius of about 4"); // test inherited method

test_eval(ctx, "var rect = new Rectangle(0, 0, 4, 4)");
duk_pop(ctx);
test_eval_expect(ctx, "rect.describe()", "A boxy rectangle, encompassing all from -2, -2 to 2, 2");

// test type-safety
Expand All @@ -85,17 +87,22 @@ void test_inheritance() {
dukglue_register_function(ctx, praiseShape, "praiseShape");

test_eval(ctx, "praiseCircle(circ)");
duk_pop(ctx);
test_eval_expect_error(ctx, "praiseCircle(rect)");

test_eval(ctx, "praiseRectangle(rect)");
duk_pop(ctx);
test_eval_expect_error(ctx, "praiseRectangle(circ)");

test_eval(ctx, "praiseShape(circ)");
test_eval(ctx, "praiseShape(rect)");
duk_pop_2(ctx);

test_eval(ctx, "circ.delete()"); // test inherited delete
test_eval(ctx, "rect.delete()");
duk_pop_2(ctx);

test_assert(duk_get_top(ctx) == 0);
duk_destroy_heap(ctx);

std::cout << "Inheritance tested OK" << std::endl;
Expand Down
6 changes: 6 additions & 0 deletions tests/test_multiple_contexts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,29 @@ void test_multiple_contexts() {
dukglue_register_method(ctx1, &A::getMeaningOfLife, "getMeaningOfLife");

test_eval(ctx1, "var test = new TestClass();");
duk_pop(ctx1);
test_eval_expect_error(ctx2, "var test = new TestClass();");

dukglue_register_constructor<B>(ctx2, "TestClass");
dukglue_register_delete<B>(ctx2);
dukglue_register_method(ctx2, &B::getMeaningOfLife, "getMeaningOfLife");

test_eval(ctx2, "var test = new TestClass();");
duk_pop(ctx2);

test_eval_expect(ctx1, "test.getMeaningOfLife()", 42);
test_eval_expect(ctx2, "test.getMeaningOfLife()", "whatever makes you happy");

test_eval(ctx1, "test.delete()");
duk_pop(ctx1);
test_eval(ctx2, "test.delete()");
duk_pop(ctx2);

test_eval_expect_error(ctx1, "test.getMeaningOfLife()");
test_eval_expect_error(ctx2, "test.getMeaningOfLife()");

test_assert(duk_get_top(ctx1) == 0);
test_assert(duk_get_top(ctx2) == 0);
duk_destroy_heap(ctx1);
duk_destroy_heap(ctx2);

Expand Down
4 changes: 4 additions & 0 deletions tests/test_primitives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,17 @@ void test_primitives() {

dukglue_register_function(ctx, test_no_args, "test_no_args");
test_eval(ctx, "test_no_args();");
duk_pop(ctx);
//test_eval_expect_error(ctx, "test_no_args(42);");

dukglue_register_function(ctx, test_one_arg, "test_one_arg");
test_eval(ctx, "test_one_arg(7);");
duk_pop(ctx);
test_eval_expect_error(ctx, "test_one_arg('butts');");

dukglue_register_function(ctx, test_two_args, "test_two_args");
test_eval(ctx, "test_two_args(2, 'butts');");
duk_pop(ctx);
//test_eval_expect_error(ctx, "test_two_args(2, 'butts', 4);");
test_eval_expect_error(ctx, "test_two_args('butts', 2);");
test_eval_expect_error(ctx, "test_two_args('butts');");
Expand All @@ -88,6 +91,7 @@ void test_primitives() {
// this shouldn't compile and give a sane error message ("Cannot return pointer to value type.")
//dukglue_register_function(ctx, get_ptr_cpp_string, "get_ptr_cpp_string");

test_assert(duk_get_top(ctx) == 0);
duk_destroy_heap(ctx);

std::cout << "Primitives tested OK" << std::endl;
Expand Down
5 changes: 5 additions & 0 deletions tests/test_properties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,35 @@ void test_properties()
dukglue_register_property(ctx, &MyClass::getValue, nullptr, "value");

test_eval(ctx, "test = new MyClass()");
duk_pop(ctx);
test_eval_expect(ctx, "test.value", 0);
test_eval_expect_error(ctx, "test.value = 42");

// test const getter + setter
dukglue_register_property(ctx, &MyClass::getValue, &MyClass::setValue, "value");
test_eval(ctx, "test.value = 42");
duk_pop(ctx);
test_eval_expect(ctx, "test.value", 42);
test_eval_expect_error(ctx, "test.value = \"food\"");

// test no getter + setter
dukglue_register_property(ctx, nullptr, &MyClass::setValue, "value");
test_eval(ctx, "test.value = 128");
duk_pop(ctx);
test_eval_expect_error(ctx, "test.value");

// test non-const getter + setter
dukglue_register_property(ctx, &MyClass::getValueNonConst, &MyClass::setValue, "value");
test_eval(ctx, "test.value = 64");
duk_pop(ctx);
test_eval_expect(ctx, "test.value", 64);

// test non-const getter + no setter
dukglue_register_property(ctx, &MyClass::getValueNonConst, nullptr, "value");
test_eval_expect_error(ctx, "test.value = 256");
test_eval_expect(ctx, "test.value", 64);

test_assert(duk_get_top(ctx) == 0);
duk_destroy_heap(ctx);

std::cout << "Properties tested OK" << std::endl;
Expand Down

0 comments on commit 2172fa5

Please sign in to comment.