1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
|
#include <HsFFI.h>
#include <lua.h>
#include <lauxlib.h>
#include <string.h>
/* ***************************************************************
* Helpers for fast element access
* ***************************************************************/
/*
** Pushes the caching table of the userdata at index `idx` to the stack.
**
** Creates and sets a new table if none has been attached to the
** userdata yet.
*/
void hslua_get_caching_table(lua_State *L, int idx)
{
if (lua_getuservalue(L, idx) == LUA_TTABLE) {
return;
}
/* No caching table set yet; create table and add to object. */
lua_pop(L, 1); /* remove nil */
int absidx = lua_absindex(L, idx);
lua_createtable(L, 0, 0);
lua_pushvalue(L, -1);
lua_setuservalue(L, absidx);
}
/*
** Retrieve a value from the wrapped userdata project. The userdata must
** be in position 1, and the key in position 2. Returns 1 if a value was
** found and is at the top of the stack, 0 otherwise. Does not clean-up
** on success.
*/
int hslua_get_from_cache(lua_State *L)
{
/* Use value in caching table if present */
hslua_get_caching_table(L, 1); /* table */
lua_pushvalue(L, 2); /* key */
if (lua_rawget(L, 3) == LUA_TNIL) {
lua_pop(L, 2); /* remove nil, caching table */
return 0;
}
/* found the key in the cache */
return 1;
}
/*
** Retrieve a value from the wrapped userdata project.
** The userdata must be in position 1, and the key in position 2.
*/
static int hsluaO_get_via_getter(lua_State *L)
{
/* Bail if there are no getterns, or no getter for the given key. */
if (luaL_getmetafield(L, 1, "getters") != LUA_TTABLE) {
return 0;
}
lua_pushvalue(L, 2); /* key */
if (lua_rawget(L, -2) == LUA_TNIL) {
lua_pop(L, 1);
return 0;
}
/* Call getter. Slow, as it calls into Haskell. */
lua_pushvalue(L, 1);
lua_call(L, 1, 1);
/* key found in wrapped userdata, add to caching table */
hslua_get_caching_table(L, 1); /* object's caching table */
lua_pushvalue(L, 2); /* key */
lua_pushvalue(L, -3); /* value */
lua_rawset(L, -3);
lua_pop(L, 1); /* pop caching table */
/* return value */
return 1;
}
/*
** Retrieve a value by using the key as the alias for a different
** property. The userdata must be in position 1, and the key in position
** 2.
*/
static int hsluaO_get_via_alias(lua_State *L)
{
if (luaL_getmetafield(L, 1, "aliases") != LUA_TTABLE) {
return 0; /* no aliases available */
}
lua_pushvalue(L, 2);
if (lua_rawget(L, -2) != LUA_TTABLE) {
lua_pop(L, 2); /* key is not an alias */
return 0; /* try a different method */
}
/* key is an alias */
lua_pushvalue(L, 1); /* start with the original object */
/* Iterate over properties; last object is on top of stack,
* list of properties is the second object. */
lua_Integer len = (lua_Integer) lua_rawlen(L, -2);
for (lua_Integer i = 1; i <= len; i++) {
lua_rawgeti(L, -2, i);
int objtype = lua_gettable(L, -2); /* get property */
lua_remove(L, -2); /* remove previous object */
if (!objtype) break; /* abort if this property of the alias is absent */
}
return 1;
}
/*
** Retrieve a method for this object. The userdata must be in position
** 1, and the key in position 2.
*/
static int hsluaO_get_method(lua_State *L)
{
if (luaL_getmetafield(L, 1, "methods") != LUA_TTABLE) {
lua_pop(L, 1);
return 0;
}
lua_pushvalue(L, 2);
lua_rawget(L, -2);
return 1;
}
/*
** Retrieves a key from a Haskell-data holding userdata value.
**
** If the key is an integer, any associated list is evaluated and the
** result is stored in the cache before it is returned.
**
** For non-integer keys, it tries the following, in order, and returns
** the first non-nil result:
**
** + Checks the userdata's uservalue table for the given key;
** + looks up a `getter` for the key and calls it with the userdata and
** key as arguments;
** + tries to lookup the key as an alias and retrieves the value of the
** alias;
** + looks up the key in the table in the `methods` metafield.
*/
int hslua_udindex(lua_State *L)
{
lua_settop(L, 2);
/* do numeric lookup for integer keys */
/* try various sources in order; return 0 if nothing is found. */
return
hslua_get_from_cache(L) ||
hsluaO_get_via_getter(L) ||
hsluaO_get_via_alias(L) ||
hsluaO_get_method(L);
}
/*
** Set value via a property alias. Assumes the stack to be in a state as
** after __newindex is called. Returns 1 on success, and 0 otherwise.
*/
static int hsluaO_set_via_alias(lua_State *L)
{
if (luaL_getmetafield(L, 1, "aliases") != LUA_TTABLE) {
return 0;
}
lua_pushvalue(L, 2);
if (lua_rawget(L, -2) != LUA_TTABLE) { /* key is an alias */
lua_pop(L, 2);
return 0;
}
lua_pushvalue(L, 1); /* start with the original object */
/* Iterate over properties; last object is on top of stack,
* list of properties is the second object. */
lua_Integer len = (lua_Integer) lua_rawlen(L, -2);
for (int i = 1; i < len; i++) {
lua_rawgeti(L, -2, i);
lua_gettable(L, -2); /* get property */
lua_remove(L, -2); /* remove previous object */
}
lua_rawgeti(L, -2, len); /* last element */
lua_pushvalue(L, 3); /* new value */
lua_settable(L, -3);
return 1;
}
/*
** Set value via a property alias. Assumes the stack to be in a state as
** after __newindex is called. Returns 1 on success, 0 if the object is
** readonly, and throws an error if there is no setter for the given
** key.
*/
static int hsluaO_set_via_setter(lua_State *L)
{
if (luaL_getmetafield(L, 1, "setters") != LUA_TTABLE)
return 0;
lua_pushvalue(L, 2); /* key */
if (lua_rawget(L, -2) != LUA_TFUNCTION) {
lua_pop(L, 1);
lua_pushliteral(L, "Cannot set unknown property.");
return lua_error(L);
}
lua_insert(L, 1);
lua_settop(L, 4); /* 1: setter, 2: ud, 3: key, 4: value */
lua_call(L, 3, 0);
return 1;
}
/*
** Sets a new value in the userdata caching table via a setter
** functions.
**
** The actual assignment is performed by a setter function stored in the
** `setter` metafield. Throws an error if no setter function can be
** found.
*/
int hslua_udnewindex(lua_State *L)
{
if (hsluaO_set_via_alias(L) || hsluaO_set_via_setter(L)) {
return 0;
}
lua_pushliteral(L, "Cannot modify read-only object.");
return lua_error(L);
}
/*
** Sets a value in the userdata's caching table (uservalue). Takes the
** same arguments as a `__newindex` function.
*/
int hslua_udsetter(lua_State *L)
{
luaL_checkany(L, 3);
lua_settop(L, 3);
hslua_get_caching_table(L, 1);
lua_insert(L, 2);
lua_rawset(L, 2);
return 0;
}
/*
** Throws an error noting that the given key is read-only.
*/
int hslua_udreadonly(lua_State *L)
{
if (lua_type(L, 2) == LUA_TSTRING && lua_checkstack(L, 3)) {
lua_pushliteral(L, "'");
lua_pushvalue(L, 2);
lua_pushliteral(L, "' is a read-only property.");
lua_concat(L, 3);
} else {
lua_pushliteral(L, "Cannot set read-only value.");
}
return lua_error(L);
}
|