#include #include #include #include /* *************************************************************** * 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); }