lua-users home
lua-l archive

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


> But I was hoping on getting away with
> using full userdata to store structs with all info on a
> cvar(pointer,flags etc.) and keep those in the global table w/o having
> to use multiple tables.

You should probably model C structures with Lua tables (or the
as-yet-undocumented proxies, which look to be quite nice). Then the problem
is much simpler because you can define metamethods for the proxy
tables/proxies without worrying about the global namespace at all.

However, scalar C proxies still strike me as an interesting question. So
here is another solution.

To pull this off, you need to at least associate a known metatable with
each different type of C variable, but I suppose you would do that anyway.
Let's suppose that the metatable has at least the keys __get and __set
whose values are (C) functions to get and set the values of the C scalars.
You need some way to create a C variable
which will create an object with the metatable. Once that is assigned to a
global, future sets and gets to that
global are deferred to the __get and __set metamethods so you can never get
rid of the binding (well, there could
be an unbind function but I didn't write that.)

These metatables pass for the "type" of the C value, and I will assume that
we have several of these. This time I'm not going to deal with the
possibility that the globals table has its own metamethods, although the
same strategy would work for that case. It just makes the code harder to
follow and it is hard enough to follow as it is :)

For efficiency I'm going to assume that you are not going to want to
redefine existing globals as C-values and that there will not be a lot of
new non C-value globals after this procedure is executed. That means that
system library functions will still be looked up without interference.

----- CUT HERE
do
  local types = {} -- table of metatables we're going to deal with
  local global_meta = {} -- metatable for globals
  local sub_meta = {} -- metatable for new globals
  local c_proxied = {} -- table of proxied values.
  local new_globals = {} -- normal globals

  -- use this function to register your metatables (types)
  function register_type(meta)
    types[meta] = true
  end

  -- We know the key doesn't exist in the globals table,
  -- which contains pre-existing globals. It
  -- might be new global, or it might be a proxied c-value.
  -- We defer the lookup to the new_globals
  -- table which has its own __index metamethod

  function global_meta.__index(t, k)
    return new_globals[k]
  end

  -- This gets invoked if the key wasn't found in the
  -- new_globals table. So it is either a
  -- proxied c-value or an undefined global

  function sub_meta.__index(t, k)
    local val = c_proxied[k]
    if val then return getmetatable(val).__get(val) end
  end

  -- With set, we need to follow a different strategy, because we need to
  -- intercept every attempt to set a global (this doesn't happen
  -- with pre-existing globals).

  function global_meta.__newindex(t, k, v)
    -- first we see if we're proxying this key, and if so we just pass on
    -- the value. c_proxied does not have metamethods, so we don't need
rawget.
    local cval = c_proxied[k]
    if cval then
        getmetatable(cval).__set(cval, v)
      else
        -- get the *value*'s type (well, metatable)
        -- and see if it is one of ours.
        local meta = getmetatable(v)
        if meta and types[meta] then
            -- It is a c_val, so we need to get rid of it from regular
globals,
            -- in case it exists, and put it into the proxied table
            new_globals[k] = nil
            c_proxied[k] = v
          else
            -- it is neither an existing c_value nor a new one. So we just
            -- stash it away
            new_globals[k] = v
        end
    end
  end

  setmetatable(getglobals(), global_meta)
  setmetatable(new_globals, sub_meta)
end

-- Quick test

do
  local date_meta = {}
  register_type(date_meta)
  -- getter returns the date according to the object's format
  function date_meta.__get(date_obj)
    return os.date(date_obj.fmt)
  end

  -- setter changes the object's format
  function date_meta.__set(date_obj, fmt)
    date_obj.fmt = fmt
  end

  -- constructor
  function Date(fmt)
    return setmetatable({fmt = fmt or "%c"}, date_meta)
  end
end

----- CUT HERE
-- sample output

> now = Date()
> print(now)
Thu Jan 16 16:41:13 2003
> gmt = Date("!%c")
> print(gmt)
Thu Jan 16 21:41:22 2003
> gmt = "!%F %R"
> print(gmt)
2003-01-16 21:41
>