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 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
|
#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 hsluaO_get_caching_table(lua_State *L, int idx)
{
int absidx = lua_absindex(L, 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 */
lua_createtable(L, 0, 0);
lua_pushvalue(L, -1);
lua_setuservalue(L, idx);
}
/*
** 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 hsluaO_get_from_cache(lua_State *L)
{
/* Use value in caching table if present */
hsluaO_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.
*/
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 */
hsluaO_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.
*/
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.
*/
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;
}
/*
** Retrieve a numerical index from this object. The userdata must be in
** position 1, and the key in position 2.
*/
int hsluaO_get_numerical(lua_State *L)
{
hsluaO_get_caching_table(L, 1);
lua_Integer requested = lua_tointeger(L, 2);
/* The __lazylistindex is set to `nil` or an integer if part of the
list is still unevaluated. If it's `false`, then all list values are
already in the cache. */
if (lua_getfield(L, 1, "__lazylistindex") == LUA_TBOOLEAN) {
lua_pop(L, 1); /* remove nil */
} else {
lua_Integer last_index = lua_tointeger(L, -1);
lua_pop(L, 1); /* pop last-index value */
if (requested > last_index &&
/* index not in cache, force lazy evaluation of list items */
luaL_getmetafield(L, 1, "lazylisteval") == LUA_TFUNCTION) {
if (lua_getfield(L, 3, "__lazylist") != LUA_TUSERDATA) {
/* lazy list thunk is missing; that shouldn't happen!! */
luaL_error(L, "Error while getting numerical index %d: "
"lazy list thunk is missing", requested);
}
lua_pushinteger(L, last_index);
lua_pushinteger(L, requested);
lua_pushvalue(L, 3); /* caching table */
lua_call(L, 4, 0); /* populate cache with evaled values */
}
}
lua_rawgeti(L, 3, requested);
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 */
return lua_isinteger(L, 2)
? (hsluaO_get_via_alias(L) || hsluaO_get_numerical(L))
/* try various sources in order; return 0 if nothing is found. */
: (hsluaO_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.
*/
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;
}
/*
** Sets a numerical index on this object. The userdata must be in
** position 1, the key in position 2, and the new value in position 3.
** Returns 1 on success and 0 otherwise.
*/
int hsluaO_set_numerical(lua_State *L)
{
hsluaO_get_caching_table(L, 1);
lua_Integer target = lua_tointeger(L, 2);
/* The `__lazylistindex` field is set to `false` if each list element
has already been evaluated and stored in the cache. Otherwise it
will be either `nil` or an integer. */
if (lua_getfield(L, 1, "__lazylistindex") == LUA_TBOOLEAN) {
lua_pop(L, 1); /* pop boolean from last-index */
} else {
/* list is not fully evaluated yet, we may have to evaluate it
further. */
lua_Integer last_index = lua_tointeger(L, -1);
lua_pop(L, 1); /* pop last-index value */
if (target > last_index) {
/* the index we want to assign has not been cached yet. Evaluation
* is forced to avoid any uncertainty about the meaning of
* `nil`-valued indices. */
lua_pushcfunction(L, &hsluaO_get_numerical);
lua_pushvalue(L, 1);
lua_pushvalue(L, 2);
lua_call(L, 2, 0);
}
}
lua_pushvalue(L, 3); /* new value */
lua_rawseti(L, -2, target); /* set in caching table */
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.
*/
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 (lua_type(L, 2) == LUA_TNUMBER) {
if (hsluaO_set_via_alias(L) || hsluaO_set_numerical(L)) {
return 0;
}
lua_pushliteral(L, "Cannot set a numerical value.");
return lua_error(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);
hsluaO_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);
}
|