diff options
Diffstat (limited to 'liblogdiag/ld-lua.c')
-rw-r--r-- | liblogdiag/ld-lua.c | 808 |
1 files changed, 808 insertions, 0 deletions
diff --git a/liblogdiag/ld-lua.c b/liblogdiag/ld-lua.c new file mode 100644 index 0000000..47a41b5 --- /dev/null +++ b/liblogdiag/ld-lua.c @@ -0,0 +1,808 @@ +/* + * ld-lua.c + * + * This file is a part of logdiag. + * Copyright Přemysl Janouch 2010 - 2011. All rights reserved. + * + * See the file LICENSE for licensing information. + * + */ + +#include <lua.h> +#include <lualib.h> +#include <lauxlib.h> + +#include "liblogdiag.h" +#include "config.h" + +#include "ld-lua-private.h" +#include "ld-lua-symbol-private.h" + + +/** + * SECTION:ld-lua + * @short_description: Lua symbol engine. + * @see_also: #LdLuaSymbol + * + * #LdLua is a symbol engine that uses Lua scripts to manage symbols. + */ + +/* + * LdLuaPrivate: + * @L: Lua state. + * + * The library contains the real function for rendering. + */ +struct _LdLuaPrivate +{ + lua_State *L; +}; + +/* registry.logdiag_symbols + * -> A table indexed by pointers to LdLuaSymbol objects + * registry.logdiag_symbols.object.render(cr) + * -> The rendering function + */ + +#define LD_LUA_LIBRARY_NAME "logdiag" +#define LD_LUA_DATA_INDEX LD_LUA_LIBRARY_NAME "_data" +#define LD_LUA_SYMBOLS_INDEX LD_LUA_LIBRARY_NAME "_symbols" + +/* + * LdLuaData: + * @self: A reference to self. + * @load_callback: A callback for newly registered symbols. + * @load_user_data: User data to be passed to the callback. + * + * Full user data to be stored in Lua registry. + */ +typedef struct _LdLuaData LdLuaData; + +struct _LdLuaData +{ + LdLua *self; + LdLuaLoadCallback load_callback; + gpointer load_user_data; +}; + +typedef struct _LdLuaDrawData LdLuaDrawData; + +struct _LdLuaDrawData +{ + LdLuaSymbol *symbol; + cairo_t *cr; + unsigned save_count; +}; + +static void ld_lua_finalize (GObject *gobject); + +static void *ld_lua_alloc (void *ud, void *ptr, size_t osize, size_t nsize); + +static int ld_lua_private_draw_cb (lua_State *L); +static int ld_lua_private_unregister_cb (lua_State *L); + +static int ld_lua_logdiag_register (lua_State *L); +static int process_registration (lua_State *L); +static gchar *get_translation (lua_State *L, int index); +static gboolean read_symbol_area (lua_State *L, int index, LdRectangle *area); +static gboolean read_terminals (lua_State *L, int index, + LdPointArray **terminals); + +static void push_cairo_object (lua_State *L, LdLuaDrawData *draw_data); +static gdouble get_cairo_scale (cairo_t *cr); +static int ld_lua_cairo_save (lua_State *L); +static int ld_lua_cairo_restore (lua_State *L); +static int ld_lua_cairo_get_line_width (lua_State *L); +static int ld_lua_cairo_set_line_width (lua_State *L); +static int ld_lua_cairo_move_to (lua_State *L); +static int ld_lua_cairo_line_to (lua_State *L); +static int ld_lua_cairo_curve_to (lua_State *L); +static int ld_lua_cairo_arc (lua_State *L); +static int ld_lua_cairo_arc_negative (lua_State *L); +static int ld_lua_cairo_new_path (lua_State *L); +static int ld_lua_cairo_new_sub_path (lua_State *L); +static int ld_lua_cairo_close_path (lua_State *L); +static int ld_lua_cairo_stroke (lua_State *L); +static int ld_lua_cairo_stroke_preserve (lua_State *L); +static int ld_lua_cairo_fill (lua_State *L); +static int ld_lua_cairo_fill_preserve (lua_State *L); +static int ld_lua_cairo_clip (lua_State *L); +static int ld_lua_cairo_clip_preserve (lua_State *L); + + +static luaL_Reg ld_lua_logdiag_lib[] = +{ + {"register", ld_lua_logdiag_register}, + {NULL, NULL} +}; + +static luaL_Reg ld_lua_cairo_table[] = +{ + {"save", ld_lua_cairo_save}, + {"restore", ld_lua_cairo_restore}, + {"get_line_width", ld_lua_cairo_get_line_width}, + {"set_line_width", ld_lua_cairo_set_line_width}, + {"move_to", ld_lua_cairo_move_to}, + {"line_to", ld_lua_cairo_line_to}, + {"curve_to", ld_lua_cairo_curve_to}, + {"arc", ld_lua_cairo_arc}, + {"arc_negative", ld_lua_cairo_arc_negative}, + {"new_path", ld_lua_cairo_new_path}, + {"new_sub_path", ld_lua_cairo_new_sub_path}, + {"close_path", ld_lua_cairo_close_path}, + {"stroke", ld_lua_cairo_stroke}, + {"stroke_preserve", ld_lua_cairo_stroke_preserve}, + {"fill", ld_lua_cairo_fill}, + {"fill_preserve", ld_lua_cairo_fill_preserve}, + {"clip", ld_lua_cairo_clip}, + {"clip_preserve", ld_lua_cairo_clip_preserve}, + {NULL, NULL} +}; + + +/* ===== Generic =========================================================== */ + +G_DEFINE_TYPE (LdLua, ld_lua, G_TYPE_OBJECT); + +static void +ld_lua_class_init (LdLuaClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = ld_lua_finalize; + + g_type_class_add_private (klass, sizeof (LdLuaPrivate)); +} + +static void +ld_lua_init (LdLua *self) +{ + lua_State *L; + LdLuaData *ud; + + self->priv = G_TYPE_INSTANCE_GET_PRIVATE + (self, LD_TYPE_LUA, LdLuaPrivate); + + L = self->priv->L = lua_newstate (ld_lua_alloc, NULL); + g_return_if_fail (L != NULL); + + /* TODO: lua_atpanic () */ + + /* Load some safe libraries. */ + lua_pushcfunction (L, luaopen_string); + lua_call (L, 0, 0); + + lua_pushcfunction (L, luaopen_table); + lua_call (L, 0, 0); + + lua_pushcfunction (L, luaopen_math); + lua_call (L, 0, 0); + + /* Load the application library. */ + luaL_register (L, LD_LUA_LIBRARY_NAME, ld_lua_logdiag_lib); + + /* Store user data to the registry. */ + ud = lua_newuserdata (L, sizeof (LdLuaData)); + ud->self = self; + ud->load_callback = NULL; + ud->load_user_data = NULL; + + lua_setfield (L, LUA_REGISTRYINDEX, LD_LUA_DATA_INDEX); + + /* Create an empty symbol table. */ + lua_newtable (L); + lua_setfield (L, LUA_REGISTRYINDEX, LD_LUA_SYMBOLS_INDEX); +} + +static void +ld_lua_finalize (GObject *gobject) +{ + LdLua *self; + + self = LD_LUA (gobject); + lua_close (self->priv->L); + + /* Chain up to the parent class. */ + G_OBJECT_CLASS (ld_lua_parent_class)->finalize (gobject); +} + +/** + * ld_lua_new: + * + * Create an instance of #LdLua. + */ +LdLua * +ld_lua_new (void) +{ + return g_object_new (LD_TYPE_LUA, NULL); +} + +static void * +ld_lua_alloc (void *ud, void *ptr, size_t osize, size_t nsize) +{ + if (!nsize) + { + g_free (ptr); + return NULL; + } + else + return g_try_realloc (ptr, nsize); +} + +/** + * ld_lua_check_file: + * @self: An #LdLua object. + * @filename: The file to be checked. + * + * Check if the given filename can be loaded by #LdLua. + */ +gboolean +ld_lua_check_file (LdLua *self, const gchar *filename) +{ + g_return_val_if_fail (LD_IS_LUA (self), FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + + return g_str_has_suffix (filename, ".lua") + && g_file_test (filename, G_FILE_TEST_IS_REGULAR); +} + +/** + * ld_lua_load_file: + * @self: An #LdLua object. + * @filename: The file to be loaded. + * @callback: A callback for newly registered symbols. + * The callee is responsible for referencing the symbol. + * @user_data: User data to be passed to the callback. + * + * Loads a file and creates #LdLuaSymbol objects for contained symbols. + * + * Returns: TRUE if no error has occured, FALSE otherwise. + */ +gboolean +ld_lua_load_file (LdLua *self, const gchar *filename, + LdLuaLoadCallback callback, gpointer user_data) +{ + gint retval; + LdLuaData *ud; + + g_return_val_if_fail (LD_IS_LUA (self), FALSE); + g_return_val_if_fail (filename != NULL, FALSE); + g_return_val_if_fail (callback != NULL, FALSE); + + /* XXX: If something from the following fails, Lua will call exit(). */ + lua_getfield (self->priv->L, LUA_REGISTRYINDEX, LD_LUA_DATA_INDEX); + ud = lua_touserdata (self->priv->L, -1); + lua_pop (self->priv->L, 1); + g_return_val_if_fail (ud != NULL, FALSE); + + ud->load_callback = callback; + ud->load_user_data = user_data; + + retval = luaL_loadfile (self->priv->L, filename); + if (retval) + goto ld_lua_lftc_fail; + + retval = lua_pcall (self->priv->L, 0, 0, 0); + if (retval) + goto ld_lua_lftc_fail; + + ud->load_callback = NULL; + ud->load_user_data = NULL; + return TRUE; + +ld_lua_lftc_fail: + g_warning ("Lua error: %s", lua_tostring (self->priv->L, -1)); + lua_remove (self->priv->L, -1); + + ud->load_callback = NULL; + ud->load_user_data = NULL; + return FALSE; +} + +/* ===== LdLuaSymbol callbacks ============================================= */ + +/** + * ld_lua_private_draw: + * @self: An #LdLua object. + * @symbol: A symbol to be drawn. + * @cr: A Cairo context to be drawn onto. + * + * Draw a symbol onto a Cairo context. + */ +void +ld_lua_private_draw (LdLua *self, LdLuaSymbol *symbol, cairo_t *cr) +{ + LdLuaDrawData data; + + g_return_if_fail (LD_IS_LUA (self)); + g_return_if_fail (LD_IS_LUA_SYMBOL (symbol)); + g_return_if_fail (cr != NULL); + + data.symbol = symbol; + data.cr = cr; + data.save_count = 0; + + if (lua_cpcall (self->priv->L, ld_lua_private_draw_cb, &data)) + { + g_warning ("Lua error: %s", lua_tostring (self->priv->L, -1)); + lua_pop (self->priv->L, 1); + } + + while (data.save_count--) + cairo_restore (cr); +} + +static int +ld_lua_private_draw_cb (lua_State *L) +{ + LdLuaDrawData *data; + + data = lua_touserdata (L, -1); + + /* Retrieve the function for rendering from the registry. */ + lua_getfield (L, LUA_REGISTRYINDEX, LD_LUA_SYMBOLS_INDEX); + lua_pushlightuserdata (L, data->symbol); + lua_gettable (L, -2); + + luaL_checktype (L, -1, LUA_TTABLE); + lua_getfield (L, -1, "render"); + luaL_checktype (L, -1, LUA_TFUNCTION); + + /* Call the function do draw the symbol. */ + push_cairo_object (L, data); + lua_pcall (L, 1, 0, 0); + return 0; +} + +/** + * ld_lua_private_unregister: + * @self: An #LdLua object. + * @symbol: A symbol to be unregistered. + * + * Unregister a symbol from the internal Lua state. + */ +void +ld_lua_private_unregister (LdLua *self, LdLuaSymbol *symbol) +{ + g_return_if_fail (LD_IS_LUA (self)); + g_return_if_fail (LD_IS_LUA_SYMBOL (symbol)); + + if (lua_cpcall (self->priv->L, ld_lua_private_unregister_cb, symbol)) + { + g_warning ("Lua error: %s", lua_tostring (self->priv->L, -1)); + lua_pop (self->priv->L, 1); + } +} + +static int +ld_lua_private_unregister_cb (lua_State *L) +{ + /* Set the entry in the symbol table to nil. */ + lua_getfield (L, LUA_REGISTRYINDEX, LD_LUA_SYMBOLS_INDEX); + lua_insert (L, -2); + lua_pushnil (L); + lua_settable (L, -3); + return 0; +} + +/* ===== Application library =============================================== */ + +static int +ld_lua_logdiag_register (lua_State *L) +{ + LdLuaData *ud; + LdLuaSymbol *symbol; + + lua_getfield (L, LUA_REGISTRYINDEX, LD_LUA_DATA_INDEX); + ud = lua_touserdata (L, -1); + lua_pop (L, 1); + g_return_val_if_fail (ud != NULL, 0); + + /* Use a protected environment, so script errors won't cause leaking + * of the symbol object. Only a failure of the last three function calls + * before lua_pcall() may cause the symbol to leak. + */ + lua_checkstack (L, 3); + symbol = g_object_new (LD_TYPE_LUA_SYMBOL, NULL); + + lua_pushlightuserdata (L, symbol); + lua_pushcclosure (L, process_registration, 1); + lua_insert (L, 1); + + /* On the stack, there are function arguments plus the function itself. */ + if (lua_pcall (L, lua_gettop (L) - 1, 0, 0)) + { + luaL_where (L, 1); + lua_insert (L, -2); + lua_concat (L, 2); + + g_warning ("Lua symbol registration failed: %s", + lua_tostring (L, -1)); + lua_pushboolean (L, FALSE); + } + else + { + /* We don't want an extra LdLua reference either. */ + symbol->priv->lua = ud->self; + g_object_ref (ud->self); + + ud->load_callback (LD_SYMBOL (symbol), ud->load_user_data); + lua_pushboolean (L, TRUE); + } + g_object_unref (symbol); + + return 1; +} + +/* + * process_registration: + * @L: A Lua state. + * + * Parse arguments, write them to a symbol object and register the object. + */ +static int +process_registration (lua_State *L) +{ + LdLuaSymbol *symbol; + gchar *human_name; + + int i, type, types[] = + {LUA_TSTRING, LUA_TTABLE, LUA_TTABLE, LUA_TTABLE, LUA_TFUNCTION}; + int n_args_needed = sizeof (types) / sizeof (int); + + if (lua_gettop (L) < n_args_needed) + return luaL_error (L, "Too few arguments."); + + for (i = 0; i < n_args_needed; i++) + if ((type = lua_type (L, i + 1)) != types[i]) + return luaL_error (L, "Bad type of argument #%d." + " Expected %s, got %s.", i + 1, + lua_typename (L, types[i]), lua_typename (L, type)); + + symbol = LD_LUA_SYMBOL (lua_touserdata (L, lua_upvalueindex (1))); + symbol->priv->name = g_strdup (lua_tostring (L, 1)); + + human_name = get_translation (L, 2); + if (!human_name) + human_name = g_strdup (symbol->priv->name); + symbol->priv->human_name = human_name; + + if (!read_symbol_area (L, 3, &symbol->priv->area)) + return luaL_error (L, "Malformed symbol area array."); + if (!read_terminals (L, 4, &symbol->priv->terminals)) + return luaL_error (L, "Malformed terminals array."); + + lua_getfield (L, LUA_REGISTRYINDEX, LD_LUA_SYMBOLS_INDEX); + lua_pushlightuserdata (L, symbol); + + lua_newtable (L); + lua_pushvalue (L, 5); + lua_setfield (L, -2, "render"); + + lua_settable (L, -3); + return 0; +} + +/* + * get_translation: + * @L: A Lua state. + * @index: Stack index of the table. + * + * Select an applicable translation from a table. + * The return value has to be freed with g_free(). + * + * Return value: The translation, if found. If none was found, returns NULL. + */ +static gchar * +get_translation (lua_State *L, int index) +{ + const gchar *const *lang; + gchar *result; + + for (lang = g_get_language_names (); *lang; lang++) + { + lua_getfield (L, 2, *lang); + if (lua_isstring (L, -1)) + { + result = g_strdup (lua_tostring (L, -1)); + lua_pop (L, 1); + return result; + } + lua_pop (L, 1); + } + return NULL; +} + +/* + * read_symbol_area: + * @L: A Lua state. + * @index: Stack index of the table. + * @area: Where the area will be returned. + * + * Read a symbol area from a Lua table. + * + * Return value: TRUE on success, FALSE on failure. + */ +static gboolean +read_symbol_area (lua_State *L, int index, LdRectangle *area) +{ + lua_Number x1, x2, y1, y2; + + if (lua_objlen (L, index) != 4) + return FALSE; + + lua_rawgeti (L, index, 1); + if (!lua_isnumber (L, -1)) + return FALSE; + x1 = lua_tonumber (L, -1); + + lua_rawgeti (L, index, 2); + if (!lua_isnumber (L, -1)) + return FALSE; + y1 = lua_tonumber (L, -1); + + lua_rawgeti (L, index, 3); + if (!lua_isnumber (L, -1)) + return FALSE; + x2 = lua_tonumber (L, -1); + + lua_rawgeti (L, index, 4); + if (!lua_isnumber (L, -1)) + return FALSE; + y2 = lua_tonumber (L, -1); + + area->x = MIN (x1, x2); + area->y = MIN (y1, y2); + area->width = ABS (x2 - x1); + area->height = ABS (y2 - y1); + + lua_pop (L, 4); + return TRUE; +} + +/* + * read_terminals: + * @L: A Lua state. + * @index: Stack index of the table. + * @area: Where the point array will be returned. + * + * Read symbol terminals from a Lua table. + * + * Return value: TRUE on success, FALSE on failure. + */ +static gboolean +read_terminals (lua_State *L, int index, LdPointArray **terminals) +{ + LdPointArray *points; + size_t num_points; + unsigned i = 0; + + num_points = lua_objlen (L, index); + points = ld_point_array_new (num_points); + + lua_pushnil (L); + while (lua_next (L, index) != 0) + { + g_assert (i < num_points); + + if (!lua_istable (L, -1) || lua_objlen (L, -1) != 2) + goto read_terminals_fail; + + lua_rawgeti (L, -1, 1); + if (!lua_isnumber (L, -1)) + goto read_terminals_fail; + points->points[i].x = lua_tonumber (L, -1); + lua_pop (L, 1); + + lua_rawgeti (L, -1, 2); + if (!lua_isnumber (L, -1)) + goto read_terminals_fail; + points->points[i].y = lua_tonumber (L, -1); + + lua_pop (L, 2); + i++; + } + *terminals = points; + return TRUE; + +read_terminals_fail: + ld_point_array_free (points); + *terminals = NULL; + return FALSE; +} + + +/* ===== Cairo ============================================================= */ + +static void +push_cairo_object (lua_State *L, LdLuaDrawData *draw_data) +{ + luaL_Reg *fn; + + /* Create a table. */ + lua_newtable (L); + + /* Add methods. */ + /* XXX: The light user data pointer gets invalid after the end of + * "render" function invocation. If the script stores the "cr" object + * in some global variable and then tries to reuse it the next time, + * the application may go SIGSEGV. + * + * The solution is creating a full user data instead, referencing + * the cairo object and dereferencing it upon garbage collection + * of the user data object. + */ + for (fn = ld_lua_cairo_table; fn->name; fn++) + { + lua_pushlightuserdata (L, draw_data); + lua_pushcclosure (L, fn->func, 1); + lua_setfield (L, -2, fn->name); + } +} + +static gdouble +get_cairo_scale (cairo_t *cr) +{ + double dx = 1, dy = 0; + + cairo_user_to_device_distance (cr, &dx, &dy); + return dx; +} + +#define LD_LUA_CAIRO_TRIVIAL(name) \ +static int \ +ld_lua_cairo_ ## name (lua_State *L) \ +{ \ + LdLuaDrawData *data; \ + data = lua_touserdata (L, lua_upvalueindex (1)); \ + cairo_ ## name (data->cr); \ + return 0; \ +} + +LD_LUA_CAIRO_TRIVIAL (new_path) +LD_LUA_CAIRO_TRIVIAL (new_sub_path) +LD_LUA_CAIRO_TRIVIAL (close_path) + +LD_LUA_CAIRO_TRIVIAL (stroke) +LD_LUA_CAIRO_TRIVIAL (stroke_preserve) +LD_LUA_CAIRO_TRIVIAL (fill) +LD_LUA_CAIRO_TRIVIAL (fill_preserve) +LD_LUA_CAIRO_TRIVIAL (clip) +LD_LUA_CAIRO_TRIVIAL (clip_preserve) + +static int +ld_lua_cairo_save (lua_State *L) +{ + LdLuaDrawData *data; + + data = lua_touserdata (L, lua_upvalueindex (1)); + if (data->save_count + 1) + { + data->save_count++; + cairo_save (data->cr); + } + return 0; +} + +static int +ld_lua_cairo_restore (lua_State *L) +{ + LdLuaDrawData *data; + + data = lua_touserdata (L, lua_upvalueindex (1)); + if (data->save_count) + { + data->save_count--; + cairo_restore (data->cr); + } + return 0; +} + +static int +ld_lua_cairo_get_line_width (lua_State *L) +{ + LdLuaDrawData *data; + + data = lua_touserdata (L, lua_upvalueindex (1)); + lua_pushnumber (L, cairo_get_line_width (data->cr) + * get_cairo_scale (data->cr)); + return 1; +} + +static int +ld_lua_cairo_set_line_width (lua_State *L) +{ + LdLuaDrawData *data; + + data = lua_touserdata (L, lua_upvalueindex (1)); + cairo_set_line_width (data->cr, luaL_checknumber (L, 1) + / get_cairo_scale (data->cr)); + return 0; +} + +static int +ld_lua_cairo_move_to (lua_State *L) +{ + LdLuaDrawData *data; + lua_Number x, y; + + data = lua_touserdata (L, lua_upvalueindex (1)); + + x = luaL_checknumber (L, 1); + y = luaL_checknumber (L, 2); + + cairo_move_to (data->cr, x, y); + return 0; +} + +static int +ld_lua_cairo_line_to (lua_State *L) +{ + LdLuaDrawData *data; + lua_Number x, y; + + data = lua_touserdata (L, lua_upvalueindex (1)); + + x = luaL_checknumber (L, 1); + y = luaL_checknumber (L, 2); + + cairo_line_to (data->cr, x, y); + return 0; +} + +static int +ld_lua_cairo_curve_to (lua_State *L) +{ + LdLuaDrawData *data; + lua_Number x1, y1, x2, y2, x3, y3; + + data = lua_touserdata (L, lua_upvalueindex (1)); + + x1 = luaL_checknumber (L, 1); + y1 = luaL_checknumber (L, 2); + x2 = luaL_checknumber (L, 3); + y2 = luaL_checknumber (L, 4); + x3 = luaL_checknumber (L, 5); + y3 = luaL_checknumber (L, 6); + + cairo_curve_to (data->cr, x1, y1, x2, y2, x3, y3); + return 0; +} + +static int +ld_lua_cairo_arc (lua_State *L) +{ + LdLuaDrawData *data; + lua_Number xc, yc, radius, angle1, angle2; + + data = lua_touserdata (L, lua_upvalueindex (1)); + + xc = luaL_checknumber (L, 1); + yc = luaL_checknumber (L, 2); + radius = luaL_checknumber (L, 3); + angle1 = luaL_checknumber (L, 4); + angle2 = luaL_checknumber (L, 5); + + cairo_arc (data->cr, xc, yc, radius, angle1, angle2); + return 0; +} + +static int +ld_lua_cairo_arc_negative (lua_State *L) +{ + LdLuaDrawData *data; + lua_Number xc, yc, radius, angle1, angle2; + + data = lua_touserdata (L, lua_upvalueindex (1)); + + xc = luaL_checknumber (L, 1); + yc = luaL_checknumber (L, 2); + radius = luaL_checknumber (L, 3); + angle1 = luaL_checknumber (L, 4); + angle2 = luaL_checknumber (L, 5); + + cairo_arc_negative (data->cr, xc, yc, radius, angle1, angle2); + return 0; +} + |