lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Hi,

L. H. de Figueiredo wrote:
> > Unfortunately this requires a large amount of code duplication
> > from liolib.c. The methods are not inheritable because of the
> > strict metatable checks (and you really need to override close()).
> 
> liolib could probably be made generic. There has been some talk about this
> on the list, but no code, AFAIK.

Well, since you asked ... :-)

Attached is a 10 line (!) patch against lauxlib.c from
Lua 5.1-work6 to allow for method inheritance on userdata
metatables. See below for a detailed explanation.

Since the above change makes inheriting from liolib.c very easy,
I wrote the posix.popen functionality, too. See the second
attachment.

I made it into a standalone module for easier testing. But
of course it's intended to be merged into lhf's POSIX module
(yes, I donate the code).

posix.popen(command [,mode]) creates a userdata object with the
"FILE*.PIPE" handle metatable. It overrides the "close" method
and the "__gc" and "__tostring" metamethods. All other methods
are inherited from the "FILE*" handle metatable provided by
liolib.c.


Ok, so here's how userdata handle inheritance works:

The idea is to use dot-separated handle names to build a simple
class hierarchy. E.g. a handle "FOO.BAR.BAZ" inherits all methods
from handle "FOO.BAR" and "FOO" (and so on). Of course you can
override individual methods by defining them in the metatable
of a subclass (assumed to be it's own __index).

luaL_newmetatable() creates a (sub)class metatable and attaches
the immediate superclass metatable to it (if defined). E.g. the
metatable for the handle "FOO.BAR.BAZ" chains to the metatable
for "FOO.BAR" and so on. No chaining is done if no immediate
superclass metatable is found (you need to make sure to load
dependent modules in the proper order).

luaL_checkudata() either checks for an exact match with the
handle name or for a partial match up to a dot. E.g. checking
for a handle "FOO" will match userdata with the same class
(same handle) or any subclass ("FOO.BAR", "FOO.BAR.BAZ" and
so on).

Obviously the userdata layout must be upwards compatible between
a class and any of its subclasses (e.g. by extending the userdata
structure at the end). And you need to override any methods from
the superclass that may make the subclass userdata inconsistent
(most likely this includes __gc).

Note: yes, this is for (simple) _userdata_ method inheritance
in C modules only. It's not applicable to pure Lua (table-based)
inheritance and probably not suitable for complex class
hierarchies.

Bye,
     Mike
--- /tmp/xx/lua-5.1-work6/src/lauxlib.c	2005-05-17 21:49:15.000000000 +0200
+++ lua-5.1-work6/src/lauxlib.c	2005-06-01 00:51:37.000000000 +0200
@@ -105,6 +105,7 @@
 
 
 LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) {
+  const char *dot;
   lua_getfield(L, LUA_REGISTRYINDEX, tname);  /* get registry.name */
   if (!lua_isnil(L, -1))  /* name already in use? */
     return 0;  /* leave previous value on top, but return 0 */
@@ -115,6 +116,12 @@
   lua_pushvalue(L, -1);
   lua_pushstring(L, tname);
   lua_rawset(L, LUA_REGISTRYINDEX);  /* registry[metatable] = name */
+  dot = strrchr(tname, '.');
+  if (dot) {  /* has a superclass? */
+    lua_pushlstring(L, tname, dot-tname);
+    lua_gettable(L, LUA_REGISTRYINDEX);  /* get superclass metatable */
+    lua_setmetatable(L, -2);  /* add to metatable chain */
+  }
   return 1;
 }
 
@@ -125,11 +132,13 @@
 
 
 LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) {
+  size_t tlen = strlen(tname);
   const char *tn;
   if (!lua_getmetatable(L, ud)) return NULL;  /* no metatable? */
   lua_rawget(L, LUA_REGISTRYINDEX);  /* get registry[metatable] */
   tn = lua_tostring(L, -1);
-  if (tn && (strcmp(tn, tname) == 0)) {
+  if (tn && (strncmp(tn, tname, tlen) == 0) &&
+      (tn[tlen] == '\0' || tn[tlen] == '.')) {  /* exact match or subclass */
     lua_pop(L, 1);
     return lua_touserdata(L, ud);
   }
/* Copyright (C) 2005 Mike Pall. All rights reserved.
 * Released as open source under the MIT/X license.
 * Donated to the Lua POSIX module.
 */

#include <stdio.h>
#include <errno.h>
#include <string.h>

#include "lua.h"
#include "lauxlib.h"


#ifndef LUA_FILEHANDLE
/* must match with what liolib.c uses */
#define LUA_FILEHANDLE		"FILE*"
#endif

/* pipe handles are a subclass of file handles and inherit their methods */
#define POSIX_PIPEHANDLE	LUA_FILEHANDLE ".PIPE"


static int pushresult(lua_State *L, int i, const char *command)
{
  if (i) {
    lua_pushboolean(L, 1);
    return 1;
  }
  else {
    lua_pushnil(L);
    if (command)
      lua_pushfstring(L, "pipe '%s': %s", command, strerror(errno));
    else
      lua_pushfstring(L, "%s", strerror(errno));
    lua_pushnumber(L, errno);
    return 3;
  }
}


static FILE **topipe(lua_State *L)
{
  FILE **pf = (FILE **)luaL_checkudata(L, 1, POSIX_PIPEHANDLE);
  if (pf == NULL) luaL_argerror(L, 1, "bad pipe");
  return pf;
}


static int pipe_close(lua_State *L)
{
  FILE **pf = topipe(L);
  FILE *f = *pf;
  if (f == NULL) luaL_error(L, "attempt to close a closed pipe");
  *pf = NULL;
  return pushresult(L, pclose(f) != -1, NULL);
}


static int pipe_gc(lua_State *L)
{
  FILE **pf = topipe(L);
  if (*pf != NULL) {  /* ignore closed pipes */
    (void)pclose(*pf);
    *pf = NULL;
  }
  return 0;
}


static int pipe_tostring(lua_State *L)
{
  FILE *f = *topipe(L);
  if (f == NULL)
    lua_pushstring(L, "pipe (closed)");
  else
    lua_pushfstring(L, "pipe (%p)", f);
  return 1;
}


static int pipe_open(lua_State *L)
{
  const char *command = luaL_checkstring(L, 1);
  const char *mode = luaL_optstring(L, 2, "r");
  FILE **pf = (FILE **)lua_newuserdata(L, sizeof(FILE *));
  *pf = NULL;
  luaL_getmetatable(L, POSIX_PIPEHANDLE);
  lua_setmetatable(L, -2);
  *pf = popen(command, mode);
  return (*pf == NULL) ? pushresult(L, 0, command) : 1;
}


static luaL_reg pipe_methods[] = {
  {"close",		pipe_close},
  {"__gc",		pipe_gc},
  {"__tostring",	pipe_tostring},
  {NULL, NULL}
};


static luaL_reg posix_funcs[] = {
  {"popen",		pipe_open},
  {NULL, NULL}
};


LUA_API int luaopen_popen(lua_State *L)
{
  luaL_newmetatable(L, POSIX_PIPEHANDLE);  /* create metatable for pipes */
  lua_pushvalue(L, -1);  /* push metatable */
  lua_setfield(L, -2, "__index");  /* metatable.__index = metatable */
  luaL_openlib(L, NULL, pipe_methods, 0);  /* add pipe methods */

  /* add popen functionality on top of the posix module */
  /* should be integrated into the posix module */
  luaL_openlib(L, "posix", posix_funcs, 0);
  return 1;
}