From a5a106f97f1fb89aeb2d25fddd3dcebd49ace09e Mon Sep 17 00:00:00 2001 From: Uli Schlachter Date: Sat, 14 Mar 2015 10:06:53 +0100 Subject: [PATCH] Make it possible for Lua to emulate arbitrary properties This makes it possible to add something similar to a __index / __newindex metamethod to all our C objects. Based on this, Lua can then easily implement arbitrary properties on our capi objects. --- common/luaclass.c | 40 ++++++++++++++++++++++++++++++++++++ common/luaclass.h | 18 ++++++++++++++++ objects/button.c | 10 +++++++++ objects/client.c | 10 +++++++++ objects/drawable.c | 10 +++++++++ objects/drawin.c | 10 +++++++++ objects/key.c | 10 +++++++++ objects/screen.c | 16 +++++++++++++++ objects/tag.c | 10 +++++++++ tests/test-miss-handlers.lua | 26 +++++++++++++++++++++++ 10 files changed, 160 insertions(+) create mode 100644 tests/test-miss-handlers.lua diff --git a/common/luaclass.c b/common/luaclass.c index 012426b0a..33c99a801 100644 --- a/common/luaclass.c +++ b/common/luaclass.c @@ -251,6 +251,8 @@ luaA_class_setup(lua_State *L, lua_class_t *class, class->parent = parent; class->tostring = NULL; class->instances = 0; + class->index_miss_handler = LUA_REFNIL; + class->newindex_miss_handler = LUA_REFNIL; signal_add(&class->signals, "new"); @@ -361,6 +363,40 @@ luaA_class_property_get(lua_State *L, lua_class_t *lua_class, int fieldidx) return NULL; } +/** Call a registered function. + * \param L The Lua VM state. + * \param handler The function to call. + * \return The number of elements pushed on stack. + */ +static +int luaA_class_call_handler(lua_State *L, int handler) +{ + /* This is based on luaA_dofunction, but allows multiple return values */ + assert(handler != LUA_REFNIL); + + int nargs = lua_gettop(L); + + /* Push error handling function and move it before args */ + lua_pushcfunction(L, luaA_dofunction_error); + lua_insert(L, - nargs - 1); + int error_func_pos = 1; + + /* push function and move it before args */ + lua_rawgeti(L, LUA_REGISTRYINDEX, handler); + lua_insert(L, - nargs - 1); + + if(lua_pcall(L, nargs, LUA_MULTRET, error_func_pos)) + { + warn("%s", lua_tostring(L, -1)); + /* Remove error function and error string */ + lua_pop(L, 2); + return 0; + } + /* Remove error function */ + lua_remove(L, error_func_pos); + return lua_gettop(L); +} + /** Generic index meta function for objects. * \param L The Lua VM state. * \return The number of elements pushed on stack. @@ -397,6 +433,8 @@ luaA_class_index(lua_State *L) } else { + if(class->index_miss_handler != LUA_REFNIL) + return luaA_class_call_handler(L, class->index_miss_handler); if(class->index_miss_property) return class->index_miss_property(L, luaA_checkudata(L, 1, class)); } @@ -427,6 +465,8 @@ luaA_class_newindex(lua_State *L) } else { + if(class->newindex_miss_handler != LUA_REFNIL) + return luaA_class_call_handler(L, class->newindex_miss_handler); if(class->newindex_miss_property) return class->newindex_miss_property(L, luaA_checkudata(L, 1, class)); } diff --git a/common/luaclass.h b/common/luaclass.h index a057108aa..4735777ed 100644 --- a/common/luaclass.h +++ b/common/luaclass.h @@ -74,6 +74,10 @@ struct lua_class_t unsigned int instances; /** Class tostring method */ lua_class_propfunc_t tostring; + /** Function to call on index misses */ + int index_miss_handler; + /** Function to call on newindex misses */ + int newindex_miss_handler; }; const char * luaA_typename(lua_State *, int); @@ -152,6 +156,18 @@ luaA_checkudataornil(lua_State *L, int udx, lua_class_t *class) { \ lua_pushnumber(L, (lua_class).instances); \ return 1; \ + } \ + \ + static inline int \ + luaA_##prefix##_set_index_miss_handler(lua_State *L) \ + { \ + return luaA_registerfct(L, 1, &(lua_class).index_miss_handler); \ + } \ + \ + static inline int \ + luaA_##prefix##_set_newindex_miss_handler(lua_State *L) \ + { \ + return luaA_registerfct(L, 1, &(lua_class).newindex_miss_handler); \ } #define LUA_CLASS_METHODS(class) \ @@ -160,6 +176,8 @@ luaA_checkudataornil(lua_State *L, int udx, lua_class_t *class) { "disconnect_signal", luaA_##class##_class_disconnect_signal }, \ { "emit_signal", luaA_##class##_class_emit_signal }, \ { "instances", luaA_##class##_class_instances }, \ + { "set_index_miss_handler", luaA_##class##_set_index_miss_handler }, \ + { "set_newindex_miss_handler", luaA_##class##_set_newindex_miss_handler }, \ #define LUA_CLASS_META \ { "__index", luaA_class_index }, \ diff --git a/objects/button.c b/objects/button.c index 29ec2a48d..5c9545665 100644 --- a/objects/button.c +++ b/objects/button.c @@ -49,6 +49,16 @@ * @function instances */ +/** Set a __index metamethod for all button instances. + * @tparam function cb The meta-method + * @function set_index_miss_handler + */ + +/** Set a __newindex metamethod for all button instances. + * @tparam function cb The meta-method + * @function set_newindex_miss_handler + */ + /** Create a new mouse button bindings. * \param L The Lua VM state. * \return The number of elements pushed on stack. diff --git a/objects/client.c b/objects/client.c index 504425a72..79ae06a74 100644 --- a/objects/client.c +++ b/objects/client.c @@ -126,6 +126,16 @@ * @function instances */ +/** Set a __index metamethod for all client instances. + * @tparam function cb The meta-method + * @function set_index_miss_handler + */ + +/** Set a __newindex metamethod for all client instances. + * @tparam function cb The meta-method + * @function set_newindex_miss_handler + */ + static area_t titlebar_get_area(client_t *c, client_titlebar_t bar); static drawable_t *titlebar_get_drawable(lua_State *L, client_t *c, int cl_idx, client_titlebar_t bar); diff --git a/objects/drawable.c b/objects/drawable.c index dff780ecd..5b5130031 100644 --- a/objects/drawable.c +++ b/objects/drawable.c @@ -49,6 +49,16 @@ * @function instances */ +/** Set a __index metamethod for all drawable instances. + * @tparam function cb The meta-method + * @function set_index_miss_handler + */ + +/** Set a __newindex metamethod for all drawable instances. + * @tparam function cb The meta-method + * @function set_newindex_miss_handler + */ + static lua_class_t drawable_class; LUA_OBJECT_FUNCS(drawable_class, drawable_t, drawable) diff --git a/objects/drawin.c b/objects/drawin.c index e10f349ba..d08ee87c9 100644 --- a/objects/drawin.c +++ b/objects/drawin.c @@ -83,6 +83,16 @@ * @function instances */ +/** Set a __index metamethod for all drawin instances. + * @tparam function cb The meta-method + * @function set_index_miss_handler + */ + +/** Set a __newindex metamethod for all drawin instances. + * @tparam function cb The meta-method + * @function set_newindex_miss_handler + */ + LUA_OBJECT_FUNCS(drawin_class, drawin_t, drawin) /** Kick out systray windows. diff --git a/objects/key.c b/objects/key.c index dc847a7af..81eb3c1e3 100644 --- a/objects/key.c +++ b/objects/key.c @@ -61,6 +61,16 @@ * @function instances */ +/** Set a __index metamethod for all key instances. + * @tparam function cb The meta-method + * @function set_index_miss_handler + */ + +/** Set a __newindex metamethod for all key instances. + * @tparam function cb The meta-method + * @function set_newindex_miss_handler + */ + static void luaA_keystore(lua_State *L, int ud, const char *str, ssize_t len) { diff --git a/objects/screen.c b/objects/screen.c index 32cdbaa6a..875748770 100644 --- a/objects/screen.c +++ b/objects/screen.c @@ -54,6 +54,22 @@ * @table screen */ +/** Get the number of instances. + * + * @return The number of screen objects alive. + * @function instances + */ + +/** Set a __index metamethod for all screen instances. + * @tparam function cb The meta-method + * @function set_index_miss_handler + */ + +/** Set a __newindex metamethod for all screen instances. + * @tparam function cb The meta-method + * @function set_newindex_miss_handler + */ + struct screen_output_t { /** The XRandR names of the output */ diff --git a/objects/tag.c b/objects/tag.c index 152f14d36..590413d4e 100644 --- a/objects/tag.c +++ b/objects/tag.c @@ -54,6 +54,16 @@ * @function instances */ +/** Set a __index metamethod for all tag instances. + * @tparam function cb The meta-method + * @function set_index_miss_handler + */ + +/** Set a __newindex metamethod for all tag instances. + * @tparam function cb The meta-method + * @function set_newindex_miss_handler + */ + /** Tag type */ struct tag { diff --git a/tests/test-miss-handlers.lua b/tests/test-miss-handlers.lua new file mode 100644 index 000000000..0d9e3a0ed --- /dev/null +++ b/tests/test-miss-handlers.lua @@ -0,0 +1,26 @@ +-- Test set_{,new}index_miss_handler + +local class = tag +local obj = class({}) + +awesome.connect_signal("debug::index::miss", error) +awesome.connect_signal("debug::newindex::miss", error) + +class.set_index_miss_handler(function(o, k) + assert(o == obj) + assert(k == "key") + return 42 +end) +assert(obj.key == 42) + +local called = false +class.set_newindex_miss_handler(function(o, k, v) + assert(o == obj) + assert(k == "key") + assert(v == 42) + called = true +end) +obj.key = 42 +assert(called) + +require("_runner").run_steps({ function() return true end })