wplua: new simple engine to integrate GObject with Lua

This commit is contained in:
George Kiagiadakis 2020-12-11 17:07:20 +02:00
parent 404d016852
commit 7d692e0246
13 changed files with 1454 additions and 1 deletions

View file

@ -1,2 +1,3 @@
subdir('wptoml')
subdir('wp')
subdir('wplua')
subdir('wptoml')

106
lib/wplua/boxed.c Normal file
View file

@ -0,0 +1,106 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "wplua.h"
#include "private.h"
#include <wp/wp.h>
static lua_CFunction
find_method_in_luaL_Reg (luaL_Reg *reg, const gchar *method)
{
if (reg) {
while (reg->name) {
if (!g_strcmp0 (method, reg->name))
return reg->func;
reg++;
}
}
return NULL;
}
static int
_wplua_gboxed___index (lua_State *L)
{
luaL_argcheck (L, wplua_isboxed (L, 1), 1,
"expected userdata storing GValue<GBoxed>");
GValue *obj_v = lua_touserdata (L, 1);
const gchar *key = luaL_checkstring (L, 2);
lua_CFunction func = NULL;
GHashTable *vtables;
lua_getglobal (L, "__wplua_vtables");
vtables = wplua_toboxed (L, -1);
lua_pop (L, 1);
/* search in registered vtables */
if (!func) {
GType type = G_VALUE_TYPE (obj_v);
while (!func && type) {
luaL_Reg *reg = g_hash_table_lookup (vtables, GUINT_TO_POINTER (type));
func = find_method_in_luaL_Reg (reg, key);
type = g_type_parent (type);
}
}
if (func) {
lua_pushcfunction (L, func);
return 1;
}
return 0;
}
void
_wplua_init_gboxed (lua_State *L)
{
static const luaL_Reg gboxed_meta[] = {
{ "__gc", _wplua_gvalue_userdata___gc },
{ "__eq", _wplua_gvalue_userdata___eq },
{ "__index", _wplua_gboxed___index },
{ NULL, NULL }
};
luaL_newmetatable (L, "GBoxed");
luaL_setfuncs (L, gboxed_meta, 0);
lua_pop (L, 1);
}
void
wplua_pushboxed (lua_State * L, GType type, gpointer object)
{
g_return_if_fail (G_TYPE_FUNDAMENTAL (type) == G_TYPE_BOXED);
GValue *v = _wplua_pushgvalue_userdata (L, type);
wp_trace_boxed (type, object, "pushing to Lua, v=%p", v);
g_value_take_boxed (v, object);
luaL_getmetatable (L, "GBoxed");
lua_setmetatable (L, -2);
}
gpointer
wplua_toboxed (lua_State *L, int idx)
{
g_return_val_if_fail (_wplua_isgvalue_userdata (L, idx, G_TYPE_BOXED), NULL);
return g_value_get_boxed ((GValue *) lua_touserdata (L, idx));
}
gpointer
wplua_checkboxed (lua_State *L, int idx, GType type)
{
if (G_UNLIKELY (!_wplua_isgvalue_userdata (L, idx, type))) {
wp_critical ("expected userdata storing GValue<%s>", g_type_name (type));
luaL_argerror (L, idx, "expected userdata storing GValue<GBoxed>");
}
return g_value_get_boxed ((GValue *) lua_touserdata (L, idx));
}
gboolean
wplua_isboxed (lua_State *L, int idx)
{
return _wplua_isgvalue_userdata (L, idx, G_TYPE_BOXED);
}

154
lib/wplua/closure.c Normal file
View file

@ -0,0 +1,154 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "wplua.h"
#include <wp/wp.h>
typedef struct _WpLuaClosure WpLuaClosure;
struct _WpLuaClosure
{
GClosure closure;
int func_ref;
};
static int
_wplua_closure_errhandler (lua_State *L)
{
wp_warning ("%s", lua_tostring (L, -1));
lua_pop (L, 1);
luaL_traceback (L, L, "traceback:\n", 1);
wp_warning ("%s", lua_tostring (L, -1));
lua_pop (L, 1);
return 0;
}
static int
_wplua_closure_pcall (lua_State *L, int nargs, int nret)
{
int hpos = lua_gettop (L) - nargs;
int ret = LUA_OK;
lua_pushcfunction (L, _wplua_closure_errhandler);
lua_insert (L, hpos);
ret = lua_pcall (L, nargs, nret, hpos);
switch (ret) {
case LUA_ERRMEM:
wp_critical ("not enough memory");
break;
case LUA_ERRERR:
wp_critical ("error running the message handler");
break;
case LUA_ERRGCMM:
wp_critical ("error running __gc");
break;
default:
break;
}
lua_remove (L, hpos);
return ret;
}
static void
_wplua_closure_marshal (GClosure *closure, GValue *return_value,
guint n_param_values, const GValue *param_values,
gpointer invocation_hint, gpointer marshal_data)
{
lua_State *L = closure->data;
int func_ref = ((WpLuaClosure *) closure)->func_ref;
/* invalid closure, skip it */
if (func_ref == LUA_NOREF || func_ref == LUA_REFNIL)
return;
/* clear the stack and stop the garbage collector for now */
lua_settop (L, 0);
lua_gc (L, LUA_GCSTOP, 0);
/* push the function */
lua_rawgeti (L, LUA_REGISTRYINDEX, func_ref);
/* push arguments */
for (guint i = 0; i < n_param_values; i++)
wplua_gvalue_to_lua (L, &param_values[i]);
/* call in protected mode */
int res = _wplua_closure_pcall (L, n_param_values, return_value ? 1 : 0);
/* handle the result */
if (res == LUA_OK && return_value && lua_gettop (L) >= 1)
wplua_lua_to_gvalue (L, 1, return_value);
/* clear the stack and clean up */
lua_settop (L, 0);
lua_gc (L, LUA_GCCOLLECT, 0);
lua_gc (L, LUA_GCRESTART, 0);
}
static void
_wplua_closure_invalidate (lua_State *L, WpLuaClosure *c)
{
wp_trace_boxed (G_TYPE_CLOSURE, c, "invalidated");
luaL_unref (L, LUA_REGISTRYINDEX, c->func_ref);
c->func_ref = LUA_NOREF;
}
/**
* wplua_function_to_closure:
*
* Make a GClosure out of a Lua function at index @idx
*
* Returns: (transfer floating): the new closure
*/
GClosure *
wplua_function_to_closure (lua_State *L, int idx)
{
g_return_val_if_fail (lua_isfunction(L, idx), NULL);
GClosure *c = g_closure_new_simple (sizeof (WpLuaClosure), L);
WpLuaClosure *wlc = (WpLuaClosure *) c;
GPtrArray *closures;
lua_getglobal (L, "__wplua_closures");
closures = wplua_toboxed (L, -1);
lua_pop (L, 1);
lua_pushvalue (L, idx);
wlc->func_ref = luaL_ref (L, LUA_REGISTRYINDEX);
wp_trace_boxed (G_TYPE_CLOSURE, c, "created, func_ref = %d", wlc->func_ref);
g_closure_set_marshal (c, _wplua_closure_marshal);
g_closure_add_invalidate_notifier (c, L,
(GClosureNotify) _wplua_closure_invalidate);
/* keep a ref in lua, so that we can invalidate
the closure when lua_State closes */
g_ptr_array_add (closures, g_closure_ref (c));
return c;
}
static void
_wplua_closure_destroy (GClosure * c)
{
g_closure_invalidate (c);
g_closure_unref (c);
}
void
_wplua_init_closure (lua_State *L)
{
GPtrArray *a = g_ptr_array_new_with_free_func (
(GDestroyNotify) _wplua_closure_destroy);
wplua_pushboxed (L, G_TYPE_PTR_ARRAY, a);
lua_setglobal (L, "__wplua_closures");
}

26
lib/wplua/meson.build Normal file
View file

@ -0,0 +1,26 @@
wplua_lib_sources = [
'boxed.c',
'closure.c',
'object.c',
'userdata.c',
'value.c',
'wplua.c',
]
wplua_lib = static_library('wplua-' + wireplumber_api_version,
wplua_lib_sources,
c_args : [
'-D_GNU_SOURCE',
'-DG_LOG_USE_STRUCTURED',
'-DG_LOG_DOMAIN="wplua"',
],
install: false,
include_directories: wp_lib_include_dir,
dependencies : [wp_dep, lua_dep],
)
wplua_dep = declare_dependency(
link_with: wplua_lib,
include_directories: wp_lib_include_dir,
dependencies: [wp_dep, lua_dep],
)

224
lib/wplua/object.c Normal file
View file

@ -0,0 +1,224 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "wplua.h"
#include "private.h"
#include <wp/wp.h>
static int
_wplua_gobject_call (lua_State *L)
{
GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
const char *sig_name = lua_tostring (L, 2);
guint n_params = lua_gettop (L) - 2;
GSignalQuery query;
guint sig_id = 0;
GQuark detail = 0;
if (G_UNLIKELY (!g_signal_parse_name (sig_name, G_TYPE_FROM_INSTANCE (obj),
&sig_id, &detail, FALSE)))
luaL_error (L, "unknown signal '%s::%s'", G_OBJECT_TYPE_NAME (obj),
sig_name);
g_signal_query (sig_id, &query);
if (G_UNLIKELY (!(query.signal_flags & G_SIGNAL_ACTION)))
luaL_error (L, "lua code is not allowed to emit non-action signal '%s::%s'",
G_OBJECT_TYPE_NAME (obj), sig_name);
if (G_UNLIKELY (query.n_params > n_params))
luaL_error (L, "not enough arguments for '%s::%s': expected %d, got %d",
G_OBJECT_TYPE_NAME (obj), sig_name, query.n_params, n_params);
GValue ret = G_VALUE_INIT;
GValue *vals = g_newa (GValue, n_params + 1);
memset (vals, 0, sizeof (GValue) * (n_params + 1));
if (query.return_type != G_TYPE_NONE)
g_value_init (&ret, query.return_type);
g_value_init_from_instance (&vals[0], obj);
for (guint i = 0; i < n_params; i++) {
g_value_init (&vals[i+1], query.param_types[i]);
wplua_lua_to_gvalue (L, i+3, &vals[i+1]);
}
g_signal_emitv (vals, sig_id, detail, &ret);
if (query.return_type != G_TYPE_NONE)
return wplua_gvalue_to_lua (L, &ret);
else
return 0;
}
static int
_wplua_gobject_connect (lua_State *L)
{
GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
const char *sig_name = luaL_checkstring (L, 2);
luaL_checktype (L, 3, LUA_TFUNCTION);
guint sig_id = 0;
GQuark detail = 0;
if (G_UNLIKELY (!g_signal_parse_name (sig_name, G_TYPE_FROM_INSTANCE (obj),
&sig_id, &detail, FALSE)))
luaL_error (L, "unknown signal '%s::%s'", G_OBJECT_TYPE_NAME (obj),
sig_name);
GClosure *closure = wplua_function_to_closure (L, 3);
gulong handler =
g_signal_connect_closure_by_id (obj, sig_id, detail, closure, FALSE);
lua_pushinteger (L, handler);
return 1;
}
static lua_CFunction
find_method_in_luaL_Reg (luaL_Reg *reg, const gchar *method)
{
if (reg) {
while (reg->name) {
if (!g_strcmp0 (method, reg->name))
return reg->func;
reg++;
}
}
return NULL;
}
static int
_wplua_gobject___index (lua_State *L)
{
GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
const gchar *key = luaL_checkstring (L, 2);
lua_CFunction func = NULL;
GHashTable *vtables;
lua_getglobal (L, "__wplua_vtables");
vtables = wplua_toboxed (L, -1);
lua_pop (L, 1);
if (!g_strcmp0 (key, "call"))
func = _wplua_gobject_call;
else if (!g_strcmp0 (key, "connect"))
func = _wplua_gobject_connect;
/* search in registered vtables */
if (!func) {
GType type = G_TYPE_FROM_INSTANCE (obj);
while (!func && type) {
luaL_Reg *reg = g_hash_table_lookup (vtables, GUINT_TO_POINTER (type));
func = find_method_in_luaL_Reg (reg, key);
type = g_type_parent (type);
}
}
/* search in registered vtables of interfaces */
if (!func) {
g_autofree GType *interfaces =
g_type_interfaces (G_TYPE_FROM_INSTANCE (obj), NULL);
GType *type = interfaces;
while (!func && *type) {
luaL_Reg *reg = g_hash_table_lookup (vtables, GUINT_TO_POINTER (*type));
func = find_method_in_luaL_Reg (reg, key);
type++;
}
}
if (func) {
lua_pushcfunction (L, func);
return 1;
}
else {
/* search in properties */
GObjectClass *klass = G_OBJECT_GET_CLASS (obj);
GParamSpec *pspec = g_object_class_find_property (klass, key);
if (pspec && (pspec->flags & G_PARAM_READABLE)) {
g_auto (GValue) v = G_VALUE_INIT;
g_value_init (&v, pspec->value_type);
g_object_get_property (obj, key, &v);
return wplua_gvalue_to_lua (L, &v);
}
}
return 0;
}
static int
_wplua_gobject___newindex (lua_State *L)
{
GObject *obj = wplua_checkobject (L, 1, G_TYPE_OBJECT);
const gchar *key = luaL_checkstring (L, 2);
/* search in properties */
GObjectClass *klass = G_OBJECT_GET_CLASS (obj);
GParamSpec *pspec = g_object_class_find_property (klass, key);
if (pspec && (pspec->flags & G_PARAM_WRITABLE)) {
g_auto (GValue) v = G_VALUE_INIT;
g_value_init (&v, pspec->value_type);
wplua_lua_to_gvalue (L, 3, &v);
g_object_set_property (obj, key, &v);
} else {
luaL_error (L, "attempted to assign unknown or non-writable property '%s'",
key);
}
return 0;
}
void
_wplua_init_gobject (lua_State *L)
{
static const luaL_Reg gobject_meta[] = {
{ "__gc", _wplua_gvalue_userdata___gc },
{ "__eq", _wplua_gvalue_userdata___eq },
{ "__index", _wplua_gobject___index },
{ "__newindex", _wplua_gobject___newindex },
{ NULL, NULL }
};
luaL_newmetatable (L, "GObject");
luaL_setfuncs (L, gobject_meta, 0);
lua_pop (L, 1);
}
void
wplua_pushobject (lua_State * L, gpointer object)
{
g_return_if_fail (G_IS_OBJECT (object));
GValue *v = _wplua_pushgvalue_userdata (L, G_TYPE_FROM_INSTANCE (object));
wp_trace_object (object, "pushing to Lua, v=%p", v);
g_value_take_object (v, object);
luaL_getmetatable (L, "GObject");
lua_setmetatable (L, -2);
}
gpointer
wplua_toobject (lua_State *L, int idx)
{
g_return_val_if_fail (_wplua_isgvalue_userdata (L, idx, G_TYPE_OBJECT), NULL);
return g_value_get_object ((GValue *) lua_touserdata (L, idx));
}
gpointer
wplua_checkobject (lua_State *L, int idx, GType type)
{
if (G_UNLIKELY (!_wplua_isgvalue_userdata (L, idx, type))) {
wp_critical ("expected userdata storing GValue<%s>", g_type_name (type));
luaL_argerror (L, idx, "expected userdata storing GValue<GObject>");
}
return g_value_get_object ((GValue *) lua_touserdata (L, idx));
}
gboolean
wplua_isobject (lua_State *L, int idx)
{
return _wplua_isgvalue_userdata (L, idx, G_TYPE_OBJECT);
}

34
lib/wplua/private.h Normal file
View file

@ -0,0 +1,34 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WPLUA_PRIVATE_H__
#define __WPLUA_PRIVATE_H__
#include "wplua.h"
G_BEGIN_DECLS
/* boxed.c */
void _wplua_init_gboxed (lua_State *L);
/* closure.c */
void _wplua_init_closure (lua_State *L);
/* object.c */
void _wplua_init_gobject (lua_State *L);
/* userdata.c */
GValue * _wplua_pushgvalue_userdata (lua_State * L, GType type);
gboolean _wplua_isgvalue_userdata (lua_State *L, int idx, GType type);
int _wplua_gvalue_userdata___gc (lua_State *L);
int _wplua_gvalue_userdata___eq (lua_State *L);
G_END_DECLS
#endif

63
lib/wplua/userdata.c Normal file
View file

@ -0,0 +1,63 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "wplua.h"
#include "private.h"
#include <wp/wp.h>
GValue *
_wplua_pushgvalue_userdata (lua_State * L, GType type)
{
GValue *v = lua_newuserdata (L, sizeof (GValue));
memset (v, 0, sizeof (GValue));
g_value_init (v, type);
return v;
}
gboolean
_wplua_isgvalue_userdata (lua_State *L, int idx, GType type)
{
GValue *v;
if (!lua_isuserdata (L, idx))
return FALSE;
if (lua_rawlen (L, idx) != sizeof (GValue))
return FALSE;
if (!(v = lua_touserdata (L, idx)))
return FALSE;
if (type != G_TYPE_NONE && !g_type_is_a (G_VALUE_TYPE (v), type))
return FALSE;
return TRUE;
}
int
_wplua_gvalue_userdata___gc (lua_State *L)
{
GValue *v = lua_touserdata (L, 1);
wp_trace_boxed (G_VALUE_TYPE (v), g_value_peek_pointer (v),
"collected, v=%p", v);
g_value_unset (v);
return 0;
}
int
_wplua_gvalue_userdata___eq (lua_State *L)
{
if (_wplua_isgvalue_userdata (L, 1, G_TYPE_NONE) &&
_wplua_isgvalue_userdata (L, 2, G_TYPE_NONE)) {
GValue *v1 = lua_touserdata (L, 1);
GValue *v2 = lua_touserdata (L, 2);
gpointer p1 = g_value_peek_pointer (v1);
gpointer p2 = g_value_peek_pointer (v2);
lua_pushboolean (L, (p1 == p2));
} else {
lua_pushboolean (L, FALSE);
}
return 1;
}

161
lib/wplua/value.c Normal file
View file

@ -0,0 +1,161 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "wplua.h"
#include "private.h"
void
wplua_lua_to_gvalue (lua_State *L, int idx, GValue *v)
{
switch (g_type_fundamental (G_VALUE_TYPE (v))) {
case G_TYPE_CHAR:
if (lua_type (L, idx) == LUA_TSTRING)
g_value_set_schar (v, *lua_tostring (L, idx));
else
g_value_set_schar (v, lua_tointeger (L, idx));
break;
case G_TYPE_UCHAR:
g_value_set_uchar (v, lua_tointeger (L, idx));
break;
case G_TYPE_INT:
g_value_set_int (v, lua_tointeger (L, idx));
break;
case G_TYPE_UINT:
g_value_set_uint (v, lua_tointeger (L, idx));
break;
case G_TYPE_LONG:
g_value_set_long (v, lua_tointeger (L, idx));
break;
case G_TYPE_ULONG:
g_value_set_ulong (v, lua_tointeger (L, idx));
break;
case G_TYPE_INT64:
g_value_set_int64 (v, lua_tointeger (L, idx));
break;
case G_TYPE_UINT64:
g_value_set_uint64 (v, lua_tonumber (L, idx));
break;
case G_TYPE_FLOAT:
g_value_set_float (v, lua_tonumber (L, idx));
break;
case G_TYPE_DOUBLE:
g_value_set_double (v, lua_tonumber (L, idx));
break;
case G_TYPE_BOOLEAN:
g_value_set_boolean (v, lua_toboolean (L, idx));
break;
case G_TYPE_STRING:
g_value_set_string (v, lua_tostring (L, idx));
break;
case G_TYPE_POINTER:
if (lua_type (L, idx) == LUA_TLIGHTUSERDATA)
g_value_set_pointer (v, lua_touserdata (L, idx));
break;
case G_TYPE_BOXED:
if (_wplua_isgvalue_userdata (L, idx, G_VALUE_TYPE (v)))
g_value_set_boxed (v, wplua_toboxed (L, idx));
break;
case G_TYPE_OBJECT:
case G_TYPE_INTERFACE:
if (_wplua_isgvalue_userdata (L, idx, G_VALUE_TYPE (v)))
g_value_set_object (v, wplua_toobject (L, idx));
break;
case G_TYPE_ENUM:
if (lua_type (L, idx) == LUA_TSTRING) {
GEnumClass *klass = g_type_class_peek (G_VALUE_TYPE (v));
GEnumValue *value = g_enum_get_value_by_nick (klass, lua_tostring (L, idx));
if (value)
g_value_set_enum (v, value->value);
} else {
g_value_set_enum (v, lua_tointeger (L, idx));
}
break;
case G_TYPE_FLAGS:
g_value_set_flags (v, lua_tointeger (L, idx));
break;
default:
break;
}
}
int
wplua_gvalue_to_lua (lua_State *L, const GValue *v)
{
switch (g_type_fundamental (G_VALUE_TYPE (v))) {
case G_TYPE_CHAR:
lua_pushinteger (L, g_value_get_schar (v));
break;
case G_TYPE_UCHAR:
lua_pushinteger (L, g_value_get_uchar (v));
break;
case G_TYPE_INT:
lua_pushinteger (L, g_value_get_int (v));
break;
case G_TYPE_UINT:
lua_pushinteger (L, g_value_get_uint (v));
break;
case G_TYPE_LONG:
lua_pushinteger (L, g_value_get_long (v));
break;
case G_TYPE_ULONG:
lua_pushinteger (L, g_value_get_ulong (v));
break;
case G_TYPE_INT64:
lua_pushinteger (L, g_value_get_int64 (v));
break;
case G_TYPE_UINT64:
lua_pushnumber (L, g_value_get_uint64 (v));
break;
case G_TYPE_FLOAT:
lua_pushnumber (L, g_value_get_float (v));
break;
case G_TYPE_DOUBLE:
lua_pushnumber (L, g_value_get_double (v));
break;
case G_TYPE_BOOLEAN:
lua_pushboolean (L, g_value_get_boolean (v));
break;
case G_TYPE_STRING:
lua_pushstring (L, g_value_get_string (v));
break;
case G_TYPE_POINTER:
lua_pushlightuserdata (L, g_value_get_pointer (v));
break;
case G_TYPE_BOXED:
wplua_pushboxed (L, G_VALUE_TYPE (v), g_value_dup_boxed (v));
break;
case G_TYPE_OBJECT:
case G_TYPE_INTERFACE:
wplua_pushobject (L, g_value_dup_object (v));
break;
case G_TYPE_ENUM: {
GEnumClass *klass = g_type_class_peek (G_VALUE_TYPE (v));
GEnumValue *value = g_enum_get_value (klass, g_value_get_enum (v));
if (value)
lua_pushstring (L, value->value_nick);
else
lua_pushinteger (L, g_value_get_enum (v));
break;
}
case G_TYPE_FLAGS:
/* FIXME: push as userdata with methods */
lua_pushinteger (L, g_value_get_flags (v));
break;
case G_TYPE_PARAM: {
GParamSpec *pspec = g_value_get_param (v);
lua_pushstring (L, pspec->name);
break;
}
case G_TYPE_VARIANT:
default:
/* FIXME implement */
lua_pushnil (L);
break;
}
return 1;
}

201
lib/wplua/wplua.c Normal file
View file

@ -0,0 +1,201 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "wplua.h"
#include "private.h"
#include <wp/wp.h>
static void
_wplua_openlibs (lua_State *L)
{
/* http://www.lua.org/manual/5.3/manual.html#luaL_requiref
* http://www.lua.org/source/5.3/linit.c.html */
static const luaL_Reg loadedlibs[] = {
{"_G", luaopen_base},
/* {LUA_LOADLIBNAME, luaopen_package}, */
/* {LUA_COLIBNAME, luaopen_coroutine}, */
{LUA_TABLIBNAME, luaopen_table},
/* {LUA_IOLIBNAME, luaopen_io}, */
/* {LUA_OSLIBNAME, luaopen_os}, */
{LUA_STRLIBNAME, luaopen_string},
{LUA_MATHLIBNAME, luaopen_math},
{LUA_UTF8LIBNAME, luaopen_utf8},
{LUA_DBLIBNAME, luaopen_debug},
{NULL, NULL}
};
const luaL_Reg *lib;
for (lib = loadedlibs; lib->func; lib++) {
luaL_requiref (L, lib->name, lib->func, 1);
lua_pop (L, 1);
}
}
static int
_wplua_typeclass___call (lua_State *L)
{
luaL_checktype (L, 1, LUA_TTABLE);
lua_pushliteral (L, "new");
if (lua_rawget (L, 1) != LUA_TFUNCTION) {
luaL_error (L, "class has no constructor");
return 0;
}
lua_replace (L, 1);
lua_call (L, lua_gettop (L) - 1, LUA_MULTRET);
return lua_gettop (L);
}
lua_State *
wplua_new (void)
{
lua_State *L = luaL_newstate ();
wp_debug ("initializing lua_State %p", L);
_wplua_openlibs (L);
_wplua_init_gboxed (L);
_wplua_init_gobject (L);
_wplua_init_closure (L);
{
static const luaL_Reg typeclass_meta[] = {
{ "__call", _wplua_typeclass___call },
{ NULL, NULL }
};
luaL_newmetatable (L, "TypeClass");
luaL_setfuncs (L, typeclass_meta, 0);
lua_pop (L, 1);
}
{
GHashTable *t = g_hash_table_new (g_direct_hash, g_direct_equal);
wplua_pushboxed (L, G_TYPE_HASH_TABLE, t);
lua_setglobal (L, "__wplua_vtables");
}
return L;
}
void
wplua_free (lua_State * L)
{
wp_debug ("closing lua_State %p", L);
lua_close (L);
}
void
wplua_register_type_methods (lua_State * L, GType type,
lua_CFunction constructor, const luaL_Reg * methods)
{
g_return_if_fail (L != NULL);
g_return_if_fail (G_TYPE_FUNDAMENTAL (type) == G_TYPE_OBJECT ||
G_TYPE_FUNDAMENTAL (type) == G_TYPE_BOXED);
/* register methods */
if (methods) {
GHashTable *vtables;
lua_getglobal (L, "__wplua_vtables");
vtables = wplua_toboxed (L, -1);
lua_pop (L, 1);
wp_debug ("Registering methods for '%s'", g_type_name (type));
if (G_UNLIKELY (g_hash_table_contains (vtables, GUINT_TO_POINTER (type)))) {
wp_critical ("type '%s' was already registered", g_type_name (type));
return;
}
g_hash_table_insert (vtables, GUINT_TO_POINTER (type), (gpointer) methods);
}
/* register constructor */
if (constructor) {
wp_debug ("Registering class for '%s'", g_type_name (type));
lua_newtable (L);
luaL_setmetatable (L, "TypeClass");
lua_pushliteral (L, "new");
lua_pushcfunction (L, constructor);
lua_settable (L, -3);
lua_setglobal (L, g_type_name (type));
}
}
gboolean
_wplua_load_buffer (lua_State * L, const gchar *buf, gsize size,
const gchar * name, GError **error)
{
int ret;ret = luaL_loadbuffer (L, buf, size, name);
if (ret != LUA_OK) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to compile: %s", lua_tostring (L, -1));
lua_pop (L, 1);
return FALSE;
}
ret = lua_pcall (L, 0, 0, 0);
if (ret != LUA_OK) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to run: %s", lua_tostring (L, -1));
lua_pop (L, 1);
return FALSE;
}
return TRUE;
}
gboolean
wplua_load_buffer (lua_State * L, const gchar *buf, gsize size, GError **error)
{
g_return_val_if_fail (L != NULL, FALSE);
g_return_val_if_fail (buf != NULL, FALSE);
g_return_val_if_fail (size != 0, FALSE);
g_autofree gchar *name =
g_strdup_printf ("buffer@%p;size=%" G_GSIZE_FORMAT, buf, size);
return _wplua_load_buffer (L, buf, size, name, error);
}
gboolean
wplua_load_uri (lua_State * L, const gchar *uri, GError **error)
{
g_autoptr (GFile) file = NULL;
g_autoptr (GBytes) bytes = NULL;
g_autoptr (GError) err = NULL;
gconstpointer data;
gsize size;
g_return_val_if_fail (L != NULL, FALSE);
g_return_val_if_fail (uri != NULL, FALSE);
file = g_file_new_for_uri (uri);
if (!(bytes = g_file_load_bytes (file, NULL, NULL, &err))) {
g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
"Failed to load '%s': %s", uri, err->message);
return FALSE;
}
data = g_bytes_get_data (bytes, &size);
return _wplua_load_buffer (L, data, size, uri, error);
}
gboolean
wplua_load_path (lua_State * L, const gchar *path, GError **error)
{
g_autofree gchar *uri = NULL;
g_return_val_if_fail (L != NULL, FALSE);
g_return_val_if_fail (path != NULL, FALSE);
if (!(uri = g_filename_to_uri (path, NULL, error)))
return FALSE;
return wplua_load_uri (L, uri, error);
}

50
lib/wplua/wplua.h Normal file
View file

@ -0,0 +1,50 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#ifndef __WPLUA_H__
#define __WPLUA_H__
#include <glib-object.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
G_BEGIN_DECLS
lua_State * wplua_new (void);
void wplua_free (lua_State * L);
void wplua_register_type_methods (lua_State * L, GType type,
lua_CFunction constructor, const luaL_Reg * methods);
/* push -> transfer full; get -> transfer none */
void wplua_pushobject (lua_State * L, gpointer object);
gpointer wplua_toobject (lua_State *L, int idx);
gpointer wplua_checkobject (lua_State *L, int idx, GType type);
gboolean wplua_isobject (lua_State *L, int idx);
/* push -> transfer full; get -> transfer none */
void wplua_pushboxed (lua_State * L, GType type, gpointer object);
gpointer wplua_toboxed (lua_State *L, int idx);
gpointer wplua_checkboxed (lua_State *L, int idx, GType type);
gboolean wplua_isboxed (lua_State *L, int idx);
/* transfer floating */
GClosure * wplua_function_to_closure (lua_State *L, int idx);
void wplua_lua_to_gvalue (lua_State *L, int idx, GValue *v);
int wplua_gvalue_to_lua (lua_State *L, const GValue *v);
gboolean wplua_load_buffer (lua_State * L, const gchar *buf, gsize size,
GError **error);
gboolean wplua_load_uri (lua_State * L, const gchar *uri, GError **error);
gboolean wplua_load_path (lua_State * L, const gchar *path, GError **error);
G_END_DECLS
#endif

View file

@ -1,4 +1,5 @@
subdir('wp')
subdir('wplua')
subdir('wptoml')
subdir('modules')
subdir('examples')

13
tests/wplua/meson.build Normal file
View file

@ -0,0 +1,13 @@
common_deps = [wplua_dep]
common_env = [
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
'WIREPLUMBER_DEBUG=7',
]
test(
'test-wplua',
executable('test-toml', 'wplua.c', dependencies: common_deps),
env: common_env,
workdir : meson.current_source_dir(),
)

419
tests/wplua/wplua.c Normal file
View file

@ -0,0 +1,419 @@
/* WirePlumber
*
* Copyright © 2020 Collabora Ltd.
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
*
* SPDX-License-Identifier: MIT
*/
#include "lua.h"
#include <wplua/wplua.h>
#include <wp/wp.h>
enum {
PROP_0,
PROP_TEST_STRING,
PROP_TEST_INT,
PROP_TEST_UINT,
PROP_TEST_INT64,
PROP_TEST_UINT64,
PROP_TEST_FLOAT,
PROP_TEST_DOUBLE,
PROP_TEST_BOOLEAN,
};
typedef struct _TestObject TestObject;
struct _TestObject
{
GObject parent;
gchar *test_string;
gint test_int;
guint test_uint;
gint64 test_int64;
guint64 test_uint64;
gfloat test_float;
gdouble test_double;
gboolean test_boolean;
};
typedef struct _TestObjectClass TestObjectClass;
struct _TestObjectClass
{
GObjectClass parent_class;
void (*change) (TestObject * self, const gchar * str, gint integer);
};
G_DEFINE_TYPE (TestObject, test_object, G_TYPE_OBJECT)
#define TEST_TYPE_OBJECT (test_object_get_type ())
_GLIB_DEFINE_AUTOPTR_CHAINUP (TestObject, GObject)
static inline TestObject * TEST_OBJECT (gpointer ptr) {
return G_TYPE_CHECK_INSTANCE_CAST (ptr, TEST_TYPE_OBJECT, TestObject);
}
static void
test_object_init (TestObject * self)
{
}
static void
test_object_finalize (GObject * object)
{
TestObject *self = TEST_OBJECT (object);
g_free (self->test_string);
G_OBJECT_CLASS (test_object_parent_class)->finalize (object);
}
static void
test_object_get_property (GObject * object, guint id, GValue * value,
GParamSpec * pspec)
{
TestObject *self = TEST_OBJECT (object);
switch (id) {
case PROP_TEST_STRING:
g_value_set_string (value, self->test_string);
break;
case PROP_TEST_INT:
g_value_set_int (value, self->test_int);
break;
case PROP_TEST_UINT:
g_value_set_uint (value, self->test_uint);
break;
case PROP_TEST_INT64:
g_value_set_int64 (value, self->test_int64);
break;
case PROP_TEST_UINT64:
g_value_set_uint64 (value, self->test_uint64);
break;
case PROP_TEST_FLOAT:
g_value_set_float (value, self->test_float);
break;
case PROP_TEST_DOUBLE:
g_value_set_double (value, self->test_double);
break;
case PROP_TEST_BOOLEAN:
g_value_set_boolean (value, self->test_boolean);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec);
break;
}
}
static void
test_object_set_property (GObject * object, guint id, const GValue * value,
GParamSpec * pspec)
{
TestObject *self = TEST_OBJECT (object);
switch (id) {
case PROP_TEST_STRING:
g_free (self->test_string);
self->test_string = g_value_dup_string (value);
break;
case PROP_TEST_INT:
self->test_int = g_value_get_int (value);
break;
case PROP_TEST_UINT:
self->test_uint = g_value_get_uint (value);
break;
case PROP_TEST_INT64:
self->test_int64 = g_value_get_int64 (value);
break;
case PROP_TEST_UINT64:
self->test_uint64 = g_value_get_uint64 (value);
break;
case PROP_TEST_FLOAT:
self->test_float = g_value_get_float (value);
break;
case PROP_TEST_DOUBLE:
self->test_double = g_value_get_double (value);
break;
case PROP_TEST_BOOLEAN:
self->test_boolean = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec);
break;
}
}
static void
test_object_change (TestObject * self, const gchar * str, gint integer)
{
g_free (self->test_string);
self->test_string = g_strdup_printf ("changed: %s", str);
g_object_notify (G_OBJECT (self), "test-string");
self->test_int = integer;
g_object_notify (G_OBJECT (self), "test-int");
gint ret = 0;
g_signal_emit_by_name (self, "acquire", &ret);
self->test_int64 = ret;
g_object_notify (G_OBJECT (self), "test-int64");
}
static void
test_object_class_init (TestObjectClass * klass)
{
GObjectClass *obj_class = (GObjectClass *) klass;
obj_class->finalize = test_object_finalize;
obj_class->get_property = test_object_get_property;
obj_class->set_property = test_object_set_property;
g_object_class_install_property (obj_class, PROP_TEST_STRING,
g_param_spec_string ("test-string", "test-string", "blurb", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (obj_class, PROP_TEST_INT,
g_param_spec_int ("test-int", "test-int", "blurb",
G_MININT, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (obj_class, PROP_TEST_UINT,
g_param_spec_uint ("test-uint", "test-uint", "blurb",
0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (obj_class, PROP_TEST_INT64,
g_param_spec_int64 ("test-int64", "test-int64", "blurb",
G_MININT64, G_MAXINT64, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (obj_class, PROP_TEST_UINT64,
g_param_spec_uint64 ("test-uint64", "test-uint64", "blurb",
0, G_MAXUINT64, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (obj_class, PROP_TEST_FLOAT,
g_param_spec_float ("test-float", "test-float", "blurb",
-20.0f, 20.0f, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (obj_class, PROP_TEST_DOUBLE,
g_param_spec_double ("test-double", "test-double", "blurb",
-20.0, 20.0, 0.0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (obj_class, PROP_TEST_BOOLEAN,
g_param_spec_boolean ("test-boolean", "test-boolean", "blurb", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_signal_new ("change", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_ACTION | G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (TestObjectClass, change), NULL, NULL, NULL,
G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT);
klass->change = test_object_change;
g_signal_new ("acquire", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL, G_TYPE_INT, 0);
}
static void
test_object_toggle (TestObject * self)
{
self->test_boolean = !self->test_boolean;
g_object_notify (G_OBJECT (self), "test-boolean");
}
static int
l_test_object_toggle (lua_State * L)
{
TestObject * self = wplua_checkobject (L, 1, TEST_TYPE_OBJECT);
test_object_toggle (self);
return 0;
}
static const luaL_Reg l_test_object_methods[] = {
{ "toggle", l_test_object_toggle },
{ NULL, NULL }
};
static int
l_test_object_new (lua_State * L)
{
wplua_pushobject (L, g_object_new (TEST_TYPE_OBJECT, NULL));
return 1;
}
static void
test_wplua_basic ()
{
lua_State *L = wplua_new ();
wplua_free (L);
}
static void
test_wplua_construct ()
{
g_autoptr (GObject) obj = NULL;
g_autoptr (GError) error = NULL;
lua_State *L = wplua_new ();
wplua_register_type_methods(L, TEST_TYPE_OBJECT,
l_test_object_new, l_test_object_methods);
const gchar code[] =
"o = TestObject.new()\n"
"assert (type(o) == 'userdata')\n";
wplua_load_buffer (L, code, sizeof (code) - 1, &error);
g_assert_no_error (error);
g_assert_cmpint (lua_getglobal (L, "o"), ==, LUA_TUSERDATA);
g_assert_true (wplua_checkobject (L, -1, TEST_TYPE_OBJECT));
g_assert_nonnull ((obj = wplua_toobject (L, -1)));
g_object_ref (obj);
g_assert_cmpint (obj->ref_count, ==, 2);
wplua_free (L);
g_assert_cmpint (obj->ref_count, ==, 1);
}
static void
test_wplua_properties ()
{
TestObject *obj = NULL;
g_autoptr (GError) error = NULL;
lua_State *L = wplua_new ();
wplua_register_type_methods(L, TEST_TYPE_OBJECT,
l_test_object_new, l_test_object_methods);
const gchar code[] =
"o = TestObject.new()\n"
"o['test-string'] = 'string from lua'\n"
"o['test-int'] = -15\n"
"o['test-uint'] = 1123456789\n"
"o['test-int64'] = -5123456789\n"
"o['test-uint64'] = 15123456789\n"
"o['test-float'] = 3.1415\n"
"o['test-double'] = 0.123456789\n"
"o['test-boolean'] = true\n";
wplua_load_buffer (L, code, sizeof (code) - 1, &error);
g_assert_no_error (error);
g_assert_cmpint (lua_getglobal (L, "o"), ==, LUA_TUSERDATA);
g_assert_true (wplua_checkobject (L, -1, TEST_TYPE_OBJECT));
g_assert_nonnull ((obj = wplua_toobject (L, -1)));
g_assert_cmpstr (obj->test_string, ==, "string from lua");
g_assert_cmpint (obj->test_int, ==, -15);
g_assert_cmpuint (obj->test_uint, ==, 1123456789);
g_assert_cmpint (obj->test_int64, ==, -5123456789);
g_assert_cmpuint (obj->test_uint64, ==, 15123456789);
g_assert_cmpfloat_with_epsilon (obj->test_float, 3.1415, 0.00001);
g_assert_cmpfloat_with_epsilon (obj->test_double, 0.123456789, 0.0000000001);
g_assert_true (obj->test_boolean);
const gchar code2[] =
"assert (o['test-string'] == 'string from lua')\n"
"assert (o['test-int'] == -15)\n"
"assert (o['test-uint'] == 1123456789)\n"
"assert (o['test-int64'] == -5123456789)\n"
"assert (o['test-uint64'] == 15123456789)\n"
"assert (math.abs (o['test-float'] - 3.1415) < 0.00001)\n"
"assert (math.abs (o['test-double'] - 0.123456789) < 0.0000000001)\n"
"assert (o['test-boolean'] == true)\n";
wplua_load_buffer (L, code2, sizeof (code2) - 1, &error);
g_assert_no_error (error);
wplua_free (L);
}
static void
test_wplua_closure ()
{
GClosure *closure;
g_autoptr (GError) error = NULL;
lua_State *L = wplua_new ();
lua_pushstring (L, "some string");
lua_setglobal (L, "expected_str");
const gchar code[] =
"f_was_called = false\n"
"function f(s)\n"
" assert(s == expected_str)\n"
" f_was_called = true\n"
"end\n";
wplua_load_buffer (L, code, sizeof (code) - 1, &error);
g_assert_no_error (error);
lua_getglobal (L, "f");
closure = wplua_function_to_closure (L, -1);
g_assert_nonnull (closure);
g_closure_ref (closure);
g_closure_sink (closure);
lua_pop (L, 1);
{
GValue s = G_VALUE_INIT;
g_value_init (&s, G_TYPE_STRING);
g_value_set_static_string (&s, "some string");
g_closure_invoke (closure, NULL, 1, &s, NULL);
}
lua_getglobal (L, "f_was_called");
g_assert_true (lua_isboolean (L, -1));
g_assert_true (lua_toboolean (L, -1));
wplua_free (L);
g_assert_true (closure->is_invalid);
g_closure_unref (closure);
}
static void
test_wplua_signals ()
{
g_autoptr (GError) error = NULL;
lua_State *L = wplua_new ();
wplua_register_type_methods(L, TEST_TYPE_OBJECT,
l_test_object_new, l_test_object_methods);
const gchar code[] =
"o = TestObject.new()\n"
"\n"
"o:connect('acquire', function (obj)\n"
" assert(obj == o)\n"
" return 42\n"
" end)\n"
"\n"
"o:connect('notify::test-string', function (obj, pspec)\n"
" assert(pspec == 'test-string')\n"
" assert(obj[pspec] == 'changed: by Lua')\n"
" end)\n"
"\n"
"o:call('change', 'by Lua', 55)\n"
"\n"
"assert(o['test-string'] == 'changed: by Lua')\n"
"assert(o['test-int'] == 55)\n"
"assert(o['test-int64'] == 42)\n"
"\n"
"o['test-boolean'] = true\n"
"o:toggle()\n"
"assert(o['test-boolean'] == false)\n";
wplua_load_buffer (L, code, sizeof (code) - 1, &error);
g_assert_no_error (error);
wplua_free (L);
}
gint
main (gint argc, gchar *argv[])
{
g_test_init (&argc, &argv, NULL);
wp_init (WP_INIT_ALL);
g_test_add_func ("/wplua/basic", test_wplua_basic);
g_test_add_func ("/wplua/construct", test_wplua_construct);
g_test_add_func ("/wplua/properties", test_wplua_properties);
g_test_add_func ("/wplua/closure", test_wplua_closure);
g_test_add_func ("/wplua/signals", test_wplua_signals);
return g_test_run ();
}