/* * ld-lua.c * * This file is a part of logdiag. * Copyright Přemysl Janouch 2010. All rights reserved. * * See the file LICENSE for licensing information. * */ #include #include #include #include #include "config.h" #include "ld-symbol.h" #include "ld-library.h" #include "ld-lua.h" #include "ld-lua-symbol.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. */ /* How does the application call the function for rendering? * registry.logdiag_symbols * -> table indexed by pointers to LdLuaSymbol objects * registry.logdiag_symbols.object.render(cr) * -> rendering function */ /* * LdLuaPrivate: * @L: Lua state. * * The library contains the real function for rendering. */ struct _LdLuaPrivate { lua_State *L; }; G_DEFINE_TYPE (LdLua, ld_lua, G_TYPE_OBJECT); static void ld_lua_finalize (GObject *gobject); static void *ld_lua_alloc (void *ud, void *ptr, size_t osize, size_t nsize); /* * 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 { LdLua *self; LdLuaLoadCallback load_callback; gpointer load_user_data; } LdLuaData; #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" typedef struct { LdLuaSymbol *symbol; cairo_t *cr; } LdLuaDrawData; 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, LdSymbolArea *area); static luaL_Reg ld_lua_logdiag_lib[] = { {"register", ld_lua_logdiag_register}, {NULL, NULL} }; static void push_cairo_object (lua_State *L, cairo_t *cr); 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_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 luaL_Reg ld_lua_cairo_table[] = { {"move_to", ld_lua_cairo_move_to}, {"line_to", ld_lua_cairo_line_to}, {"stroke", ld_lua_cairo_stroke}, {"stroke_preserve", ld_lua_cairo_stroke_preserve}, {"fill", ld_lua_cairo_fill}, {"fill_preserve", ld_lua_cairo_fill_preserve}, {NULL, NULL} }; /* ===== Generic =========================================================== */ 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 symbols 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); 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)); data.symbol = symbol; data.cr = cr; 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); } } 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->cr); 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); symbol = g_object_new (LD_TYPE_LUA_SYMBOL, NULL); /* Use a protected environment, so script errors won't cause leaking * of the symbol object. Only the failure of one of the following three * function calls may cause the symbol to leak. */ 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."); /* TODO: Read and set the terminals. */ 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, LdSymbolArea *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); return TRUE; } /* ===== Cairo ============================================================= */ static void push_cairo_object (lua_State *L, cairo_t *cr) { 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 will go SIGSEGV. */ for (fn = ld_lua_cairo_table; fn->name; fn++) { lua_pushlightuserdata (L, cr); lua_pushcclosure (L, fn->func, 1); lua_setfield (L, -2, fn->name); } } /* TODO: More functions. Possibly put it into another file * and generate it automatically. */ static int ld_lua_cairo_move_to (lua_State *L) { cairo_t *cr; lua_Number x, y; cr = lua_touserdata (L, lua_upvalueindex (1)); x = luaL_checknumber (L, 1); y = luaL_checknumber (L, 2); cairo_move_to (cr, x, y); return 0; } static int ld_lua_cairo_line_to (lua_State *L) { cairo_t *cr; lua_Number x, y; cr = lua_touserdata (L, lua_upvalueindex (1)); x = luaL_checknumber (L, 1); y = luaL_checknumber (L, 2); cairo_line_to (cr, x, y); return 0; } static int ld_lua_cairo_stroke (lua_State *L) { cairo_t *cr; cr = lua_touserdata (L, lua_upvalueindex (1)); cairo_stroke (cr); return 0; } static int ld_lua_cairo_stroke_preserve (lua_State *L) { cairo_t *cr; cr = lua_touserdata (L, lua_upvalueindex (1)); cairo_stroke_preserve (cr); return 0; } static int ld_lua_cairo_fill (lua_State *L) { cairo_t *cr; cr = lua_touserdata (L, lua_upvalueindex (1)); cairo_fill (cr); return 0; } static int ld_lua_cairo_fill_preserve (lua_State *L) { cairo_t *cr; cr = lua_touserdata (L, lua_upvalueindex (1)); cairo_fill_preserve (cr); return 0; }