packages feed

hslua-objectorientation 2.3.1 → 2.4.0

raw patch · 10 files changed

+886/−675 lines, 10 filesdep −QuickCheckdep −exceptionsdep −lua-arbitrarydep ~containersPVP ok

version bump matches the API change (PVP)

Dependencies removed: QuickCheck, exceptions, lua-arbitrary, mtl, quickcheck-instances, tasty-hunit, tasty-quickcheck

Dependency ranges changed: containers

API changes (from Hackage documentation)

- HsLua.ObjectOrientation: Absent :: Possible a
- HsLua.ObjectOrientation: Actual :: a -> Possible a
- HsLua.ObjectOrientation: Add :: Operation
- HsLua.ObjectOrientation: Band :: Operation
- HsLua.ObjectOrientation: Bnot :: Operation
- HsLua.ObjectOrientation: Bor :: Operation
- HsLua.ObjectOrientation: Bxor :: Operation
- HsLua.ObjectOrientation: Call :: Operation
- HsLua.ObjectOrientation: Concat :: Operation
- HsLua.ObjectOrientation: CustomOperation :: Name -> Operation
- HsLua.ObjectOrientation: Div :: Operation
- HsLua.ObjectOrientation: Eq :: Operation
- HsLua.ObjectOrientation: Idiv :: Operation
- HsLua.ObjectOrientation: Index :: Operation
- HsLua.ObjectOrientation: IntegerIndex :: Integer -> AliasIndex
- HsLua.ObjectOrientation: Le :: Operation
- HsLua.ObjectOrientation: Len :: Operation
- HsLua.ObjectOrientation: Lt :: Operation
- HsLua.ObjectOrientation: Mod :: Operation
- HsLua.ObjectOrientation: Mul :: Operation
- HsLua.ObjectOrientation: Newindex :: Operation
- HsLua.ObjectOrientation: Pairs :: Operation
- HsLua.ObjectOrientation: Pow :: Operation
- HsLua.ObjectOrientation: Property :: (a -> LuaE e NumResults) -> Maybe (StackIndex -> a -> LuaE e a) -> Text -> TypeSpec -> Property e a
- HsLua.ObjectOrientation: Shl :: Operation
- HsLua.ObjectOrientation: Shr :: Operation
- HsLua.ObjectOrientation: StringIndex :: Name -> AliasIndex
- HsLua.ObjectOrientation: Sub :: Operation
- HsLua.ObjectOrientation: Tostring :: Operation
- HsLua.ObjectOrientation: UDTypeWithList :: Name -> [(Operation, fn)] -> Map Name (Property e a) -> Map Name fn -> Map AliasIndex Alias -> Maybe (ListSpec e a itemtype) -> (fn -> LuaE e ()) -> UDTypeWithList e fn a itemtype
- HsLua.ObjectOrientation: Unm :: Operation
- HsLua.ObjectOrientation: [propertyDescription] :: Property e a -> Text
- HsLua.ObjectOrientation: [propertyGet] :: Property e a -> a -> LuaE e NumResults
- HsLua.ObjectOrientation: [propertySet] :: Property e a -> Maybe (StackIndex -> a -> LuaE e a)
- HsLua.ObjectOrientation: [propertyType] :: Property e a -> TypeSpec
- HsLua.ObjectOrientation: [udAliases] :: UDTypeWithList e fn a itemtype -> Map AliasIndex Alias
- HsLua.ObjectOrientation: [udFnPusher] :: UDTypeWithList e fn a itemtype -> fn -> LuaE e ()
- HsLua.ObjectOrientation: [udListSpec] :: UDTypeWithList e fn a itemtype -> Maybe (ListSpec e a itemtype)
- HsLua.ObjectOrientation: [udMethods] :: UDTypeWithList e fn a itemtype -> Map Name fn
- HsLua.ObjectOrientation: [udName] :: UDTypeWithList e fn a itemtype -> Name
- HsLua.ObjectOrientation: [udOperations] :: UDTypeWithList e fn a itemtype -> [(Operation, fn)]
- HsLua.ObjectOrientation: [udProperties] :: UDTypeWithList e fn a itemtype -> Map Name (Property e a)
- HsLua.ObjectOrientation: alias :: AliasIndex -> Text -> [AliasIndex] -> Member e fn a
- HsLua.ObjectOrientation: data AliasIndex
- HsLua.ObjectOrientation: data Member e fn a
- HsLua.ObjectOrientation: data Operation
- HsLua.ObjectOrientation: data Possible a
- HsLua.ObjectOrientation: data Property e a
- HsLua.ObjectOrientation: data UDTypeWithList e fn a itemtype
- HsLua.ObjectOrientation: deftypeGeneric' :: Pusher e fn -> Name -> [(Operation, fn)] -> [Member e fn a] -> Maybe (ListSpec e a itemtype) -> UDTypeWithList e fn a itemtype
- HsLua.ObjectOrientation: initTypeGeneric :: LuaError e => (UDTypeWithList e fn a itemtype -> LuaE e ()) -> UDTypeWithList e fn a itemtype -> LuaE e Name
- HsLua.ObjectOrientation: instance Data.String.IsString HsLua.ObjectOrientation.AliasIndex
- HsLua.ObjectOrientation: instance GHC.Classes.Eq HsLua.ObjectOrientation.AliasIndex
- HsLua.ObjectOrientation: instance GHC.Classes.Ord HsLua.ObjectOrientation.AliasIndex
- HsLua.ObjectOrientation: methodGeneric :: Name -> fn -> Member e fn a
- HsLua.ObjectOrientation: peekUDGeneric :: LuaError e => UDTypeWithList e fn a itemtype -> Peeker e a
- HsLua.ObjectOrientation: possibleProperty :: LuaError e => Name -> Text -> (Pusher e b, a -> Possible b) -> (Peeker e b, a -> b -> Possible a) -> Member e fn a
- HsLua.ObjectOrientation: possibleProperty' :: LuaError e => Name -> TypeSpec -> Text -> (Pusher e b, a -> Possible b) -> (Peeker e b, a -> b -> Possible a) -> Member e fn a
- HsLua.ObjectOrientation: property :: LuaError e => Name -> Text -> (Pusher e b, a -> b) -> (Peeker e b, a -> b -> a) -> Member e fn a
- HsLua.ObjectOrientation: property' :: LuaError e => Name -> TypeSpec -> Text -> (Pusher e b, a -> b) -> (Peeker e b, a -> b -> a) -> Member e fn a
- HsLua.ObjectOrientation: pushUDGeneric :: LuaError e => (UDTypeWithList e fn a itemtype -> LuaE e ()) -> UDTypeWithList e fn a itemtype -> a -> LuaE e ()
- HsLua.ObjectOrientation: readonly :: Name -> Text -> (Pusher e b, a -> b) -> Member e fn a
- HsLua.ObjectOrientation: readonly' :: Name -> TypeSpec -> Text -> (Pusher e b, a -> b) -> Member e fn a
- HsLua.ObjectOrientation: type Alias = [AliasIndex]
- HsLua.ObjectOrientation: type ListSpec e a itemtype = ((Pusher e itemtype, a -> [itemtype]), (Peeker e itemtype, a -> [itemtype] -> a))
- HsLua.ObjectOrientation: udDocs :: UDTypeWithList e fn a itemtype -> TypeDocs
- HsLua.ObjectOrientation: udTypeSpec :: UDTypeWithList e fn a itemtype -> TypeSpec
+ HsLua.ObjectOrientation.Generic: Absent :: Possible a
+ HsLua.ObjectOrientation.Generic: Actual :: a -> Possible a
+ HsLua.ObjectOrientation.Generic: Add :: Operation
+ HsLua.ObjectOrientation.Generic: Band :: Operation
+ HsLua.ObjectOrientation.Generic: Bnot :: Operation
+ HsLua.ObjectOrientation.Generic: Bor :: Operation
+ HsLua.ObjectOrientation.Generic: Bxor :: Operation
+ HsLua.ObjectOrientation.Generic: Call :: Operation
+ HsLua.ObjectOrientation.Generic: Concat :: Operation
+ HsLua.ObjectOrientation.Generic: CustomOperation :: Name -> Operation
+ HsLua.ObjectOrientation.Generic: Div :: Operation
+ HsLua.ObjectOrientation.Generic: Eq :: Operation
+ HsLua.ObjectOrientation.Generic: Idiv :: Operation
+ HsLua.ObjectOrientation.Generic: Index :: Operation
+ HsLua.ObjectOrientation.Generic: IntegerIndex :: Integer -> AliasIndex
+ HsLua.ObjectOrientation.Generic: Le :: Operation
+ HsLua.ObjectOrientation.Generic: Len :: Operation
+ HsLua.ObjectOrientation.Generic: Lt :: Operation
+ HsLua.ObjectOrientation.Generic: Mod :: Operation
+ HsLua.ObjectOrientation.Generic: Mul :: Operation
+ HsLua.ObjectOrientation.Generic: Newindex :: Operation
+ HsLua.ObjectOrientation.Generic: Pairs :: Operation
+ HsLua.ObjectOrientation.Generic: Pow :: Operation
+ HsLua.ObjectOrientation.Generic: Property :: (a -> LuaE e NumResults) -> Maybe (StackIndex -> a -> LuaE e a) -> Text -> TypeSpec -> Property e a
+ HsLua.ObjectOrientation.Generic: Shl :: Operation
+ HsLua.ObjectOrientation.Generic: Shr :: Operation
+ HsLua.ObjectOrientation.Generic: StringIndex :: Name -> AliasIndex
+ HsLua.ObjectOrientation.Generic: Sub :: Operation
+ HsLua.ObjectOrientation.Generic: Tostring :: Operation
+ HsLua.ObjectOrientation.Generic: UDType :: Name -> [(Operation, fn)] -> Map Name (Property e a) -> Map Name fn -> Map AliasIndex Alias -> UDTypeHooks e fn a -> (fn -> LuaE e ()) -> UDTypeGeneric e fn a
+ HsLua.ObjectOrientation.Generic: UDTypeHooks :: Int -> LuaE e () -> (a -> StackIndex -> Peek e a) -> (a -> LuaE e ()) -> UDTypeHooks e fn a
+ HsLua.ObjectOrientation.Generic: Unm :: Operation
+ HsLua.ObjectOrientation.Generic: [hookMetatableSetup] :: UDTypeHooks e fn a -> LuaE e ()
+ HsLua.ObjectOrientation.Generic: [hookPeekUD] :: UDTypeHooks e fn a -> a -> StackIndex -> Peek e a
+ HsLua.ObjectOrientation.Generic: [hookPushUD] :: UDTypeHooks e fn a -> a -> LuaE e ()
+ HsLua.ObjectOrientation.Generic: [hookUservalues] :: UDTypeHooks e fn a -> Int
+ HsLua.ObjectOrientation.Generic: [propertyDescription] :: Property e a -> Text
+ HsLua.ObjectOrientation.Generic: [propertyGet] :: Property e a -> a -> LuaE e NumResults
+ HsLua.ObjectOrientation.Generic: [propertySet] :: Property e a -> Maybe (StackIndex -> a -> LuaE e a)
+ HsLua.ObjectOrientation.Generic: [propertyType] :: Property e a -> TypeSpec
+ HsLua.ObjectOrientation.Generic: [udAliases] :: UDTypeGeneric e fn a -> Map AliasIndex Alias
+ HsLua.ObjectOrientation.Generic: [udFnPusher] :: UDTypeGeneric e fn a -> fn -> LuaE e ()
+ HsLua.ObjectOrientation.Generic: [udHooks] :: UDTypeGeneric e fn a -> UDTypeHooks e fn a
+ HsLua.ObjectOrientation.Generic: [udMethods] :: UDTypeGeneric e fn a -> Map Name fn
+ HsLua.ObjectOrientation.Generic: [udName] :: UDTypeGeneric e fn a -> Name
+ HsLua.ObjectOrientation.Generic: [udOperations] :: UDTypeGeneric e fn a -> [(Operation, fn)]
+ HsLua.ObjectOrientation.Generic: [udProperties] :: UDTypeGeneric e fn a -> Map Name (Property e a)
+ HsLua.ObjectOrientation.Generic: alias :: AliasIndex -> Text -> [AliasIndex] -> Member e fn a
+ HsLua.ObjectOrientation.Generic: data AliasIndex
+ HsLua.ObjectOrientation.Generic: data Member e fn a
+ HsLua.ObjectOrientation.Generic: data Operation
+ HsLua.ObjectOrientation.Generic: data Possible a
+ HsLua.ObjectOrientation.Generic: data Property e a
+ HsLua.ObjectOrientation.Generic: data UDTypeGeneric e fn a
+ HsLua.ObjectOrientation.Generic: data UDTypeHooks e fn a
+ HsLua.ObjectOrientation.Generic: deftypeGeneric' :: Pusher e fn -> Name -> [(Operation, fn)] -> [Member e fn a] -> UDTypeHooks e fn a -> UDTypeGeneric e fn a
+ HsLua.ObjectOrientation.Generic: emptyHooks :: UDTypeHooks e fn a
+ HsLua.ObjectOrientation.Generic: initType :: LuaError e => UDTypeGeneric e fn a -> LuaE e Name
+ HsLua.ObjectOrientation.Generic: instance Data.String.IsString HsLua.ObjectOrientation.Generic.AliasIndex
+ HsLua.ObjectOrientation.Generic: instance GHC.Classes.Eq HsLua.ObjectOrientation.Generic.AliasIndex
+ HsLua.ObjectOrientation.Generic: instance GHC.Classes.Ord HsLua.ObjectOrientation.Generic.AliasIndex
+ HsLua.ObjectOrientation.Generic: methodGeneric :: Name -> fn -> Member e fn a
+ HsLua.ObjectOrientation.Generic: peekUDGeneric :: LuaError e => UDTypeGeneric e fn a -> Peeker e a
+ HsLua.ObjectOrientation.Generic: possibleProperty :: LuaError e => Name -> Text -> (Pusher e b, a -> Possible b) -> (Peeker e b, a -> b -> Possible a) -> Member e fn a
+ HsLua.ObjectOrientation.Generic: possibleProperty' :: LuaError e => Name -> TypeSpec -> Text -> (Pusher e b, a -> Possible b) -> (Peeker e b, a -> b -> Possible a) -> Member e fn a
+ HsLua.ObjectOrientation.Generic: property :: LuaError e => Name -> Text -> (Pusher e b, a -> b) -> (Peeker e b, a -> b -> a) -> Member e fn a
+ HsLua.ObjectOrientation.Generic: property' :: LuaError e => Name -> TypeSpec -> Text -> (Pusher e b, a -> b) -> (Peeker e b, a -> b -> a) -> Member e fn a
+ HsLua.ObjectOrientation.Generic: pushUDGeneric :: LuaError e => UDTypeGeneric e fn a -> a -> LuaE e ()
+ HsLua.ObjectOrientation.Generic: readonly :: Name -> Text -> (Pusher e b, a -> b) -> Member e fn a
+ HsLua.ObjectOrientation.Generic: readonly' :: Name -> TypeSpec -> Text -> (Pusher e b, a -> b) -> Member e fn a
+ HsLua.ObjectOrientation.Generic: type Alias = [AliasIndex]
+ HsLua.ObjectOrientation.Generic: udDocs :: UDTypeGeneric e fn a -> TypeDocs
+ HsLua.ObjectOrientation.Generic: udTypeSpec :: UDTypeGeneric e fn a -> TypeSpec
+ HsLua.ObjectOrientation.ListType: listExtension :: LuaError e => ListSpec e a itemtype -> UDTypeHooks e fn a
+ HsLua.ObjectOrientation.ListType: type ListSpec e a itemtype = ((Pusher e itemtype, a -> [itemtype]), (Peeker e itemtype, a -> [itemtype] -> a))
+ HsLua.ObjectOrientation.ListType: type UDTypeWithList e fn a itemtype = UDTypeGeneric e fn a
- HsLua.ObjectOrientation: type UDType e fn a = UDTypeWithList e fn a Void
+ HsLua.ObjectOrientation: type UDType e fn a = UDTypeGeneric e fn a

Files

CHANGELOG.md view
@@ -2,6 +2,36 @@  `hslua-objectorientation` uses [PVP Versioning][]. +## hslua-objectorientation-2.4.0++Released 2025-06-23.++-   The code has been reorganized: the new submodules+    `HsLua.ObjectOrientation.Generic` and+    `HsLua.ObjectOrientation.ListType` have been added.++-   The `UDTypeGeneric` type has been updated, the definitions for+    Lua types can now contain additional hooks to modify the+    behavior when initializing the type and when pushing and+    pulling objects to and from Lua.++-   The function `pushUDGeneric` is modified and no longer takes a+    `pushDocs` parameter. Use the new type hooks instead.++-   Removed the extra hook from `initTypeGeneric` and renamed it+    to `initType`. The hook, if one is needed, must now be part of+    the UDTypeGeneric object.++-   The default `__index` and `__newindex` functions have been+    simplified and no longer handle integer keys. Consequently,+    list-like types now need their own `__index` and `__newindex`+    functions. These can handle numerical indices and fall back to+    the default functions for other keys.++    The new file `hslobj.h` contains the headers of the default+    access functions. It is added to the `install-includes` cabal+    field.+ ## hslua-objectorientation-2.3.1  Released 2024-01-18.
LICENSE view
@@ -1,4 +1,4 @@-Copyright © 1994-2022 Lua.org, PUC-Rio.+Copyright © 1994-2023 Lua.org, PUC-Rio. Copyright © 2007-2012 Gracjan Polak Copyright © 2012-2015 Ömer Sinan Ağacan Copyright © 2016-2024 Albert Krewinkel
+ cbits/hsllist.c view
@@ -0,0 +1,115 @@+#include <lua.h>+#include <lauxlib.h>+#include "hslobj.h"++/* ***************************************************************+ * Lazy List object access+ * ***************************************************************/++/*+** Retrieve a numerical index from this object. The userdata must be in+** position 1, and the key in position 2.+*/+static int hsluaL_get_numerical(lua_State *L)+{+  hslua_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.+**+** Otherwise, the default method for key retrieval is used.+*/+int hslua_list_udindex(lua_State *L)+{+  lua_settop(L, 2);+  /* do numeric lookup for integer keys */+  return lua_isinteger(L, 2)+    ? (hsluaL_get_numerical(L))+    /* Fall back to the default hslua index method for non-integer keys */+    : hslua_udindex(L);+}++/*+** 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.+*/+static int hsluaL_set_numerical(lua_State *L)+{+  hslua_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, &hsluaL_get_numerical);+      lua_pushvalue(L, 1);              /* userdata object */+      lua_pushvalue(L, 2);              /* numerical key */+      lua_call(L, 2, 0);+    }+  }+  lua_pushvalue(L, 3);                  /* new value */+  lua_rawseti(L, -2, target);           /* set in caching table */+  return 1;  /* signal success */+}++/*+** Sets a value for a list-like object. Behaves like normal element+** access, but also handles (numerical) list indices.+*/+int hslua_list_udnewindex(lua_State *L)+{+  lua_settop(L, 3);+  if (lua_type(L, 2) == LUA_TNUMBER) {+    if (hsluaL_set_numerical(L)) {+      return 0;+    }+    lua_pushliteral(L, "Cannot set a numerical value.");+    return lua_error(L);+  }++  return hslua_udnewindex(L);+}
cbits/hslobj.c view
@@ -13,18 +13,19 @@ ** 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)+void hslua_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 */++  int absidx = lua_absindex(L, idx);   lua_createtable(L, 0, 0);   lua_pushvalue(L, -1);-  lua_setuservalue(L, idx);+  lua_setuservalue(L, absidx); }  /*@@ -33,10 +34,10 @@ ** 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)+int hslua_get_from_cache(lua_State *L) {   /* Use value in caching table if present */-  hsluaO_get_caching_table(L, 1);       /* table */+  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 */@@ -51,7 +52,7 @@ ** 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)+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) {@@ -68,7 +69,7 @@   lua_call(L, 1, 1);    /* key found in wrapped userdata, add to caching table */-  hsluaO_get_caching_table(L, 1);       /* object's 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);@@ -82,7 +83,7 @@ ** property. The userdata must be in position 1, and the key in position ** 2. */-int hsluaO_get_via_alias(lua_State *L)+static int hsluaO_get_via_alias(lua_State *L) {   if (luaL_getmetafield(L, 1, "aliases") != LUA_TTABLE) {     return 0;             /* no aliases available */@@ -111,7 +112,7 @@ ** 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)+static int hsluaO_get_method(lua_State *L) {   if (luaL_getmetafield(L, 1, "methods") != LUA_TTABLE) {     lua_pop(L, 1);@@ -123,42 +124,6 @@ }  /*-** 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@@ -178,20 +143,19 @@ {   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));+  /* 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.  */-int hsluaO_set_via_alias(lua_State *L)+static int hsluaO_set_via_alias(lua_State *L) {   if (luaL_getmetafield(L, 1, "aliases") != LUA_TTABLE) {     return 0;@@ -217,48 +181,12 @@ }  /*-** 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)+static int hsluaO_set_via_setter(lua_State *L) {   if (luaL_getmetafield(L, 1, "setters") != LUA_TTABLE)     return 0;@@ -286,13 +214,6 @@  */ 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;   }@@ -309,7 +230,7 @@ {   luaL_checkany(L, 3);   lua_settop(L, 3);-  hsluaO_get_caching_table(L, 1);+  hslua_get_caching_table(L, 1);   lua_insert(L, 2);   lua_rawset(L, 2);   return 0;
+ cbits/hslobj.h view
@@ -0,0 +1,22 @@+#ifndef hslobj_h+#define hslobj_h++#include <lua.h>++/* ***************************************************************+ * Helpers for fast element access+ * ***************************************************************/++/* Object field getter */+int hslua_udindex(lua_State *L);++/* Object field setter */+int hslua_udnewindex(lua_State *L);++/* Lazy access to object's caching table */+int hslua_get_caching_table(lua_State *L, int index);++/* Get a value from the object's uservalue cache */+int hslua_get_from_cache(lua_State *L);++#endif
hslua-objectorientation.cabal view
@@ -1,6 +1,6 @@ cabal-version:       2.2 name:                hslua-objectorientation-version:             2.3.1+version:             2.4.0 synopsis:            Object orientation tools for HsLua description:         Expose Haskell objects to Lua with an object oriented                      interface.@@ -15,15 +15,15 @@ build-type:          Simple extra-source-files:  README.md                    , CHANGELOG.md-tested-with:         GHC == 8.4.4-                   , GHC == 8.6.5-                   , GHC == 8.8.4+tested-with:         GHC == 8.8.4                    , GHC == 8.10.7                    , GHC == 9.0.2                    , GHC == 9.2.8                    , GHC == 9.4.8-                   , GHC == 9.6.3-                   , GHC == 9.8.1+                   , GHC == 9.6.7+                   , GHC == 9.8.4+                   , GHC == 9.10.2+                   , GHC == 9.12.2  source-repository head   type:                git@@ -33,34 +33,37 @@ common common-options   default-language:    Haskell2010   build-depends:       base              >= 4.11   && < 5-                     , bytestring        >= 0.10.2 && < 0.13-                     , containers        >= 0.5.9  && < 0.8-                     , exceptions        >= 0.8    && < 0.11                      , hslua-core        >= 2.2.1  && < 2.4                      , hslua-marshalling >= 2.2.1  && < 2.4                      , hslua-typing      >= 0.1    && < 0.2-                     , mtl               >= 2.2    && < 2.4-                     , text              >= 1.2    && < 2.2+   ghc-options:         -Wall+                       -Wcpp-undef+                       -Werror=missing-home-modules+                       -Widentities                        -Wincomplete-record-updates+                       -Wincomplete-uni-patterns                        -Wnoncanonical-monad-instances+                       -Wpartial-fields                        -Wredundant-constraints-  if impl(ghc >= 8.2)-    ghc-options:         -Wcpp-undef-                         -Werror=missing-home-modules-  if impl(ghc >= 8.4)-    ghc-options:         -Widentities-                         -Wincomplete-uni-patterns-                         -Wpartial-fields-                         -fhide-source-paths+                       -fhide-source-paths+  if impl(ghc >= 8.10)+    ghc-options:         -Wunused-packages+  if impl(ghc >= 9.0)+    ghc-options:         -Winvalid-haddock+   other-extensions:    OverloadedStrings                      , TypeApplications  library   import:              common-options   exposed-modules:     HsLua.ObjectOrientation+                     , HsLua.ObjectOrientation.Generic+                     , HsLua.ObjectOrientation.ListType                      , HsLua.ObjectOrientation.Operation   hs-source-dirs:      src+  build-depends:       containers           >= 0.5.9  && < 0.9+                     , text                 >= 1.2    && < 2.2   default-extensions:  LambdaCase                      , StrictData   other-extensions:    AllowAmbiguousTypes@@ -68,7 +71,11 @@                      , FlexibleInstances                      , MultiParamTypeClasses                      , ScopedTypeVariables+  includes:            hslobj.h+  install-includes:    hslobj.h+  include-dirs:        cbits   c-sources:           cbits/hslobj.c+                     , cbits/hsllist.c  test-suite test-hslua-objectorientation   import:              common-options@@ -78,10 +85,6 @@   ghc-options:         -threaded -Wno-unused-do-bind   other-modules:       HsLua.ObjectOrientationTests   build-depends:       hslua-objectorientation-                     , lua-arbitrary        >= 1.0-                     , QuickCheck           >= 2.7-                     , quickcheck-instances >= 0.3+                     , bytestring           >= 0.10.2 && < 0.13                      , tasty                >= 0.11                      , tasty-hslua          >= 1.0-                     , tasty-hunit          >= 0.9-                     , tasty-quickcheck     >= 0.8
src/HsLua/ObjectOrientation.hs view
@@ -1,7 +1,6 @@-{-# LANGUAGE ForeignFunctionInterface #-}-{-# LANGUAGE OverloadedStrings   #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications    #-}+{-# LANGUAGE FlexibleInstances     #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables   #-} {-| Module      : HsLua.ObjectOrientation Copyright   : © 2021-2024 Albert Krewinkel@@ -17,85 +16,21 @@ -} module HsLua.ObjectOrientation   ( UDType-  , UDTypeWithList (..)-    -- * Defining types   , deftypeGeneric-  , deftypeGeneric'-    -- ** Methods-  , methodGeneric-    -- ** Properties-  , property-  , property'-  , possibleProperty-  , possibleProperty'-  , readonly-  , readonly'-    -- ** Aliases-  , alias-    -- * Marshaling-  , peekUDGeneric-  , pushUDGeneric-  , initTypeGeneric-    -- * Type docs-  , udDocs-  , udTypeSpec-    -- * Helper types for building-  , Member-  , Property (..)-  , Operation (..)-  , ListSpec-  , Possible (..)-  , Alias-  , AliasIndex (..)+  , module HsLua.ObjectOrientation.Generic+  , module HsLua.ObjectOrientation.ListType   ) where -import Control.Monad ((<$!>), forM_, void, when)-import Data.Maybe (mapMaybe)-import Data.Map (Map)-import Data.String (IsString (..))-import Data.Text (Text)-import Data.Void (Void)-import Foreign.Ptr (FunPtr)-import HsLua.Core as Lua-import HsLua.Marshalling-import HsLua.ObjectOrientation.Operation-import HsLua.Typing ( TypeDocs (..), TypeSpec (..), anyType, userdataType )-import qualified Data.Map.Strict as Map-import qualified HsLua.Core.Unsafe as Unsafe-import qualified HsLua.Core.Utf8 as Utf8---- | A userdata type, capturing the behavior of Lua objects that wrap--- Haskell values. The type name must be unique; once the type has been--- used to push or retrieve a value, the behavior can no longer be--- modified through this type.------ This type includes methods to define how the object should behave as--- a read-only list of type @itemtype@.-data UDTypeWithList e fn a itemtype = UDTypeWithList-  { udName          :: Name-  , udOperations    :: [(Operation, fn)]-  , udProperties    :: Map Name (Property e a)-  , udMethods       :: Map Name fn-  , udAliases       :: Map AliasIndex Alias-  , udListSpec      :: Maybe (ListSpec e a itemtype)-  , udFnPusher      :: fn -> LuaE e ()-  }---- | Pair of pairs, describing how a type can be used as a Lua list. The--- first pair describes how to push the list items, and how the list is--- extracted from the type; the second pair contains a method to--- retrieve list items, and defines how the list is used to create an--- updated value.-type ListSpec e a itemtype =-  ( (Pusher e itemtype, a -> [itemtype])-  , (Peeker e itemtype, a -> [itemtype] -> a)-  )+import HsLua.Core (Name)+import HsLua.Marshalling (Pusher)+import HsLua.ObjectOrientation.Generic+import HsLua.ObjectOrientation.ListType  -- | A userdata type, capturing the behavior of Lua objects that wrap -- Haskell values. The type name must be unique; once the type has been -- used to push or retrieve a value, the behavior can no longer be -- modified through this type.-type UDType e fn a = UDTypeWithList e fn a Void+type UDType e fn a = UDTypeGeneric e fn a  -- | Defines a new type, defining the behavior of objects in Lua. -- Note that the type name must be unique.@@ -105,462 +40,4 @@                -> [Member e fn a]       -- ^ methods                -> UDType e fn a deftypeGeneric pushFunction name ops members =-  deftypeGeneric' pushFunction name ops members Nothing---- | Defines a new type that could also be treated as a list; defines--- the behavior of objects in Lua. Note that the type name must be--- unique.-deftypeGeneric' :: Pusher e fn          -- ^ function pusher-                -> Name                 -- ^ type name-                -> [(Operation, fn)]    -- ^ operations-                -> [Member e fn a]      -- ^ methods-                -> Maybe (ListSpec e a itemtype)  -- ^ list access-                -> UDTypeWithList e fn a itemtype-deftypeGeneric' pushFunction name ops members mbListSpec = UDTypeWithList-  { udName          = name-  , udOperations    = ops-  , udProperties    = Map.fromList $ mapMaybe mbproperties members-  , udMethods       = Map.fromList $ mapMaybe mbmethods members-  , udAliases       = Map.fromList $ mapMaybe mbaliases members-  , udListSpec      = mbListSpec-  , udFnPusher      = pushFunction-  }-  where-    mbproperties = \case-      MemberProperty n p -> Just (n, p)-      _ -> Nothing-    mbmethods = \case-      MemberMethod n m -> Just (n, m)-      _ -> Nothing-    mbaliases = \case-      MemberAlias n a -> Just (n, a)-      _ -> Nothing---- | A read- and writable property on a UD object.-data Property e a = Property-  { propertyGet :: a -> LuaE e NumResults-  , propertySet :: Maybe (StackIndex -> a -> LuaE e a)-  , propertyDescription :: Text-  , propertyType :: TypeSpec-  }---- | Alias for a different property of this or of a nested object.-type Alias = [AliasIndex]---- | Index types allowed in aliases (strings and integers)-data AliasIndex-  = StringIndex Name-  | IntegerIndex Lua.Integer-  deriving (Eq, Ord)--instance IsString AliasIndex where-  fromString = StringIndex . fromString---- | A type member, either a method or a variable.-data Member e fn a-  = MemberProperty Name (Property e a)-  | MemberMethod Name fn-  | MemberAlias AliasIndex Alias---- | Use a documented function as an object method.-methodGeneric :: Name -> fn -> Member e fn a-methodGeneric = MemberMethod---- | A property or method which may be available in some instances but--- not in others.-data Possible a-  = Actual a-  | Absent---- | Declares a new read- and writable typed property.-property' :: LuaError e-          => Name                       -- ^ property name-          -> TypeSpec                   -- ^ property type-          -> Text                       -- ^ property description-          -> (Pusher e b, a -> b)       -- ^ how to get the property value-          -> (Peeker e b, a -> b -> a)  -- ^ how to set a new property value-          -> Member e fn a-property' name typespec desc (push, get) (peek, set) =-  possibleProperty' name typespec desc-    (push, Actual . get)-    (peek, \a b -> Actual (set a b))---- | Declares a new read- and writable property.-property :: LuaError e-         => Name                       -- ^ property name-         -> Text                       -- ^ property description-         -> (Pusher e b, a -> b)       -- ^ how to get the property value-         -> (Peeker e b, a -> b -> a)  -- ^ how to set a new property value-         -> Member e fn a-property name desc (push, get) (peek, set) =-  possibleProperty name desc-    (push, Actual . get)-    (peek, \a b -> Actual (set a b))---- | Declares a new read- and writable property which is not always--- available.-possibleProperty :: LuaError e-  => Name                               -- ^ property name-  -> Text                               -- ^ property description-  -> (Pusher e b, a -> Possible b)      -- ^ how to get the property value-  -> (Peeker e b, a -> b -> Possible a) -- ^ how to set a new property value-  -> Member e fn a-possibleProperty name = possibleProperty' name anyType---- | Declares a new read- and writable property which is not always--- available.-possibleProperty' :: LuaError e-  => Name                               -- ^ property name-  -> TypeSpec                           -- ^ type of the property value-  -> Text                               -- ^ property description-  -> (Pusher e b, a -> Possible b)      -- ^ how to get the property value-  -> (Peeker e b, a -> b -> Possible a) -- ^ how to set a new property value-  -> Member e fn a-possibleProperty' name typespec desc (push, get) (peek, set) =-  MemberProperty name $-  Property-  { propertyGet = \x -> do-      case get x of-        Actual y -> NumResults 1 <$ push y-        Absent   -> return (NumResults 0)-  , propertySet = Just $ \idx x -> do-      value  <- forcePeek $ peek idx-      case set x value of-        Actual y -> return y-        Absent   -> failLua $ "Trying to set unavailable property "-                            <> Utf8.toString (fromName name)-                            <> "."-  , propertyType = typespec-  , propertyDescription = desc-  }---- | Creates a read-only object property. Attempts to set the value will--- cause an error.-readonly' :: Name                 -- ^ property name-          -> TypeSpec             -- ^ property type-          -> Text                 -- ^ property description-          -> (Pusher e b, a -> b) -- ^ how to get the property value-          -> Member e fn a-readonly' name typespec desc (push, get) = MemberProperty name $-  Property-  { propertyGet = \x -> do-      push $ get x-      return (NumResults 1)-  , propertySet = Nothing-  , propertyType = typespec-  , propertyDescription = desc-  }---- | Creates a read-only object property. Attempts to set the value will--- cause an error.-readonly :: Name                 -- ^ property name-         -> Text                 -- ^ property description-         -> (Pusher e b, a -> b) -- ^ how to get the property value-         -> Member e fn a-readonly name = readonly' name anyType---- | Define an alias for another, possibly nested, property.-alias :: AliasIndex    -- ^ property alias-      -> Text          -- ^ description-      -> [AliasIndex]  -- ^ sequence of nested properties-      -> Member e fn a-alias name _desc = MemberAlias name---- | Ensures that the type has been fully initialized, i.e., that all--- metatables have been created and stored in the registry. Returns the--- name of the initialized type.------ The @hook@ can be used to perform additional setup operations. The--- function is called as the last step after the type metatable has been--- initialized: the fully initialized metatable will be at the top of--- the stack at that point. Note that the hook will /not/ be called if--- the type's metatable already existed before this function was--- invoked.-initTypeGeneric :: LuaError e-                => (UDTypeWithList e fn a itemtype -> LuaE e ())-                -> UDTypeWithList e fn a itemtype-                -> LuaE e Name-initTypeGeneric hook ty = do-  pushUDMetatable hook ty-  pop 1-  return (udName ty)---- | Pushes the metatable for the given type to the Lua stack. Creates--- the new table afresh on the first time it is needed, and retrieves it--- from the registry after that.--------- A @hook@ can be used to perform additional setup operations. The--- function is called as the last step after the type metatable has been--- initialized: the fully initialized metatable will be at the top of--- the stack at that point. Note that the hook will /not/ be called if--- the type's metatable already existed before this function was--- invoked.-pushUDMetatable :: LuaError e-  => (UDTypeWithList e fn a itemtype -> LuaE e ())  -- ^ @hook@-  -> UDTypeWithList e fn a itemtype-  -> LuaE e ()-pushUDMetatable hook ty = do-  created <- newudmetatable (udName ty)-  when created $ do-    add (metamethodName Index)    $ pushcfunction hslua_udindex_ptr-    add (metamethodName Newindex) $ pushcfunction hslua_udnewindex_ptr-    add (metamethodName Pairs)    $ pushHaskellFunction (pairsFunction ty)-    forM_ (udOperations ty) $ \(op, f) -> do-      add (metamethodName op) $ udFnPusher ty f-    add "getters" $ pushGetters ty-    add "setters" $ pushSetters ty-    add "methods" $ pushMethods ty-    add "aliases" $ pushAliases ty-    case udListSpec ty of-      Nothing -> pure ()-      Just ((pushItem, _), _) -> do-        add "lazylisteval" $ pushHaskellFunction (lazylisteval pushItem)-    hook ty-  where-    add :: LuaError e => Name -> LuaE e () -> LuaE e ()-    add name op = do-      pushName name-      op-      rawset (nth 3)---- | Retrieves a key from a Haskell-data holding userdata value.------ Does 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;------   - Looks up the key in the table in the @methods@ metafield.-foreign import ccall "hslobj.c &hslua_udindex"-  hslua_udindex_ptr :: FunPtr (State -> IO NumResults)---- | 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.-foreign import ccall "hslobj.c &hslua_udnewindex"-  hslua_udnewindex_ptr :: FunPtr (State -> IO NumResults)---- | Sets a value in the userdata's caching table (uservalue). Takes the--- same arguments as a @__newindex@ function.-foreign import ccall "hslobj.c &hslua_udsetter"-  hslua_udsetter_ptr :: FunPtr (State -> IO NumResults)---- | Throws an error nothing that the given key is read-only.-foreign import ccall "hslobj.c &hslua_udreadonly"-  hslua_udreadonly_ptr :: FunPtr (State -> IO NumResults)---- | Pushes the metatable's @getters@ field table.-pushGetters :: LuaError e => UDTypeWithList e fn a itemtype -> LuaE e ()-pushGetters ty = do-  newtable-  void $ flip Map.traverseWithKey (udProperties ty) $ \name prop -> do-    pushName name-    pushHaskellFunction $ forcePeek (peekUDGeneric ty 1) >>= propertyGet prop-    rawset (nth 3)---- | Pushes the metatable's @setters@ field table.-pushSetters :: LuaError e => UDTypeWithList e fn a itemtype -> LuaE e ()-pushSetters ty = do-  newtable-  void $ flip Map.traverseWithKey (udProperties ty) $ \name prop -> do-    pushName name-    pushcfunction $ case propertySet prop of-      Just _  -> hslua_udsetter_ptr-      Nothing -> hslua_udreadonly_ptr-    rawset (nth 3)---- | Pushes the metatable's @methods@ field table.-pushMethods :: LuaError e => UDTypeWithList e fn a itemtype -> LuaE e ()-pushMethods ty = do-  newtable-  void $ flip Map.traverseWithKey (udMethods ty) $ \name fn -> do-    pushName name-    udFnPusher ty fn-    rawset (nth 3)--pushAliases :: LuaError e => UDTypeWithList e fn a itemtype -> LuaE e ()-pushAliases ty = do-  newtable-  void $ flip Map.traverseWithKey (udAliases ty) $ \name propSeq -> do-    pushAliasIndex name-    pushList pushAliasIndex propSeq-    rawset (nth 3)--pushAliasIndex :: Pusher e AliasIndex-pushAliasIndex = \case-  StringIndex name -> pushName name-  IntegerIndex n   -> pushIntegral n---- | Pushes the function used to iterate over the object's key-value--- pairs in a generic *for* loop.-pairsFunction :: forall e fn a itemtype. LuaError e-              => UDTypeWithList e fn a itemtype -> LuaE e NumResults-pairsFunction ty = do-  obj <- forcePeek $ peekUDGeneric ty (nthBottom 1)-  let pushMember = \case-        MemberProperty name prop -> do-          pushName name-          getresults <- propertyGet prop obj-          if getresults == 0-            then 0 <$ pop 1  -- property is absent, don't push anything-            else return $ getresults + 1-        MemberMethod name f -> do-          pushName name-          udFnPusher ty f-          return 2-        MemberAlias{} -> fail "aliases are not full properties"-  pushIterator pushMember $-    map (uncurry MemberProperty) (Map.toAscList (udProperties ty)) ++-    map (uncurry MemberMethod) (Map.toAscList (udMethods ty))---- | Evaluate part of a lazy list. Takes the following arguments, in--- this order:------ 1. userdata wrapping the unevalled part of the lazy list--- 2. index of the last evaluated element--- 3. index of the requested element--- 4. the caching table-lazylisteval :: forall itemtype e. LuaError e-             => Pusher e itemtype -> LuaE e NumResults-lazylisteval pushItem = do-  munevaled <- fromuserdata @[itemtype] (nthBottom 1) lazyListStateName-  mcurindex <- tointeger (nthBottom 2)-  mnewindex <- tointeger (nthBottom 3)-  case (munevaled, mcurindex, mnewindex) of-    (Just unevaled, Just curindex, Just newindex) -> do-      let numElems = fromIntegral $ max (newindex - curindex) 0-          (as, rest) = splitAt numElems unevaled-      if null rest-        then do-          -- no more elements in list; unset variable-          pushName "__lazylistindex"-          pushBool False-          rawset (nthBottom 4)-        else do-          -- put back remaining unevalled list-          void $ putuserdata @[itemtype] (nthBottom 1) lazyListStateName rest-          pushName "__lazylistindex"-          pushinteger (curindex + fromIntegral (length as))-          rawset (nthBottom 4)-      -- push evaluated elements-      forM_ (zip [(curindex + 1)..] as) $ \(i, a) -> do-        pushItem a-        rawseti (nthBottom 4) i-      return (NumResults 0)-    _ -> pure (NumResults 0)---- | Name of the metatable used for unevaluated lazy list rema-lazyListStateName :: Name-lazyListStateName = "HsLua unevalled lazy list"---- | Pushes a userdata value of the given type.-pushUDGeneric :: LuaError e-  => (UDTypeWithList e fn a itemtype -> LuaE e ()) -- ^ push docs-  -> UDTypeWithList e fn a itemtype                -- ^ userdata type-  -> a                                             -- ^ value to push-  -> LuaE e ()-pushUDGeneric pushDocs ty x = do-  newhsuserdatauv x 1-  pushUDMetatable pushDocs ty-  setmetatable (nth 2)-  -- add list as value in caching table-  case udListSpec ty of-    Nothing -> pure ()-    Just ((_, toList), _) -> do-      newtable-      pushName "__lazylist"-      newhsuserdatauv (toList x) 1-      void (newudmetatable lazyListStateName)-      setmetatable (nth 2)-      rawset (nth 3)-      void (setiuservalue (nth 2) 1)---- | Retrieves a userdata value of the given type.-peekUDGeneric :: LuaError e => UDTypeWithList e fn a itemtype -> Peeker e a-peekUDGeneric ty idx = do-  let name = udName ty-  x <- reportValueOnFailure name (`fromuserdata` name) idx-  (`lastly` pop 1) $ liftLua (getiuservalue idx 1) >>= \case-    TypeTable -> do-      -- set list-      xWithList <- maybe pure setList (udListSpec ty) x-      liftLua $ do-        pushnil-        setProperties (udProperties ty) xWithList-    _ -> return x---- | Retrieves object properties from a uservalue table and sets them on--- the given value. Expects the uservalue table at the top of the stack.-setProperties :: LuaError e => Map Name (Property e a) -> a -> LuaE e a-setProperties props x = do-  hasNext <- Unsafe.next (nth 2)-  if not hasNext-    then return x-    else ltype (nth 2) >>= \case-      TypeString -> do-        propName <- forcePeek $ peekName (nth 2)-        case Map.lookup propName props >>= propertySet of-          Nothing -> pop 1 *> setProperties props x-          Just setter -> do-            x' <- setter top x-            pop 1-            setProperties props x'-      _ -> x <$ pop 1---- | Gets a list from a uservalue table and sets it on the given value.--- Expects the uservalue (i.e., caching) table to be at the top of the--- stack.-setList :: forall itemtype e a. LuaError e-        => ListSpec e a itemtype -> a-        -> Peek e a-setList (_pushspec, (peekItem, updateList)) x = (x `updateList`) <$!> do-  liftLua (getfield top "__lazylistindex") >>= \case-    TypeBoolean -> do-      -- list had been fully evaluated-      liftLua $ pop 1-      peekList peekItem top-    _ -> do-      let getLazyList = do-            liftLua (getfield top "__lazylist") >>= \case-              TypeUserdata -> pure ()-              _ -> failPeek "unevaled items of lazy list cannot be peeked"-            (`lastly` pop 1) $ reportValueOnFailure-              lazyListStateName-              (\idx -> fromuserdata @[itemtype] idx lazyListStateName)-              top-      mlastIndex <- liftLua (tointeger top <* pop 1)-      let itemsAfter = case mlastIndex of-            Nothing -> const getLazyList-            Just lastIndex -> \i ->-              if i <= lastIndex-              then liftLua (rawgeti top i) >>= \case-                TypeNil -> [] <$ liftLua (pop 1)-                _ -> do-                  y <- peekItem top `lastly` pop 1-                  (y:) <$!> itemsAfter (i + 1)-              else getLazyList-      itemsAfter 1------- Typing------- | Returns documentation for this type.-udDocs :: UDTypeWithList e fn a itemtype-       -> TypeDocs-udDocs ty = TypeDocs-  { typeDescription = mempty-  , typeSpec = userdataType-  , typeRegistry = Just (udName ty)-  }---- | Type specifier for a UDType-udTypeSpec :: UDTypeWithList e fn a itemtype-           -> TypeSpec-udTypeSpec = NamedType . udName+  deftypeGeneric' pushFunction name ops members emptyHooks
+ src/HsLua/ObjectOrientation/Generic.hs view
@@ -0,0 +1,481 @@+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE OverloadedStrings        #-}+{-# LANGUAGE ScopedTypeVariables      #-}+{-|+Module      : HsLua.ObjectOrientation.Generic+Copyright   : © 2021-2024 Albert Krewinkel+License     : MIT+Maintainer  : Albert Krewinkel <tarleb@hslua.org>++This module provides types and functions to use Haskell values as+userdata objects in Lua. These objects wrap a Haskell value and provide+methods and properties to interact with the Haskell value.++The terminology in this module refers to the userdata values as /UD+objects/, and to their type as /UD type/.+-}+module HsLua.ObjectOrientation.Generic+  ( UDTypeGeneric (..)+    -- * Defining types+  , deftypeGeneric'+    -- ** Methods+  , methodGeneric+    -- ** Properties+  , property+  , property'+  , possibleProperty+  , possibleProperty'+  , readonly+  , readonly'+    -- ** Aliases+  , alias+    -- ** Type extension+  , UDTypeHooks (..)+  , emptyHooks+    -- * Marshaling+  , peekUDGeneric+  , pushUDGeneric+  , initType+    -- * Type docs+  , udDocs+  , udTypeSpec+    -- * Helper types for building+  , Member+  , Property (..)+  , Operation (..)+  , Possible (..)+  , Alias+  , AliasIndex (..)+  ) where++import Control.Monad (forM_, void, when)+import Data.Maybe (mapMaybe)+import Data.Map (Map)+import Data.String (IsString (..))+import Data.Text (Text)+import Foreign.Ptr (FunPtr)+import HsLua.Core as Lua+import HsLua.Marshalling+import HsLua.ObjectOrientation.Operation+import HsLua.Typing ( TypeDocs (..), TypeSpec (..), anyType, userdataType )+import qualified Data.Map.Strict as Map+import qualified HsLua.Core.Unsafe as Unsafe+import qualified HsLua.Core.Utf8 as Utf8++-- | A userdata type, capturing the behavior of Lua objects that wrap+-- Haskell values. The type name must be unique; once the type has been+-- used to push or retrieve a value, the behavior can no longer be+-- modified through this type.+--+-- This type includes methods to define how the object should behave as+-- a read-only list of type @itemtype@.+data UDTypeGeneric e fn a = UDType+  { udName          :: Name+  , udOperations    :: [(Operation, fn)]+  , udProperties    :: Map Name (Property e a)+  , udMethods       :: Map Name fn+  , udAliases       :: Map AliasIndex Alias+  , udHooks         :: UDTypeHooks e fn a+  , udFnPusher      :: fn -> LuaE e ()+  }++-- | Extensions for userdata types.+data UDTypeHooks e fn a =+  UDTypeHooks+  { hookUservalues :: Int+    -- ^ Number of uservalues required for this extension, *including* the+    -- uservalue for the default caching table.++  , hookMetatableSetup :: LuaE e ()+    -- ^ Run after the metatable has been set up; the new metatable is+    -- at the top of the stack when this hook is run.++  , hookPeekUD :: a+               -> StackIndex+               -> Peek e a+    -- ^ Peek extra data and modify the peeked object++  , hookPushUD :: a -> LuaE e ()+    -- ^ Push extra data+  }++-- | No extension.+emptyHooks :: UDTypeHooks e fn a+emptyHooks = UDTypeHooks+  { hookMetatableSetup = return ()++  , hookPeekUD = \x _idx -> return x++  , hookPushUD = \_x -> return ()++  , hookUservalues = 1+  }++-- | Defines a new "Lua type" and sets the behavior of the Lua object+-- instances. It's possible to pass custom type extensions, which modify+-- the default object behavior. Furthermore, the /function pusher/+-- parameter controls how functions are marshaled to Lua.+--+-- Note that the type name must be unique.+deftypeGeneric' :: Pusher e fn          -- ^ function pusher+                -> Name                 -- ^ type name+                -> [(Operation, fn)]    -- ^ operations+                -> [Member e fn a]      -- ^ methods+                -> UDTypeHooks e fn a   -- ^ behavior modifying hooks+                -> UDTypeGeneric e fn a+deftypeGeneric' pushFunction name ops members extension = UDType+  { udName          = name+  , udOperations    = ops+  , udProperties    = Map.fromList $ mapMaybe mbproperties members+  , udMethods       = Map.fromList $ mapMaybe mbmethods members+  , udAliases       = Map.fromList $ mapMaybe mbaliases members+  , udHooks         = extension+  , udFnPusher      = pushFunction+  }+  where+    mbproperties = \case+      MemberProperty n p -> Just (n, p)+      _ -> Nothing+    mbmethods = \case+      MemberMethod n m -> Just (n, m)+      _ -> Nothing+    mbaliases = \case+      MemberAlias n a -> Just (n, a)+      _ -> Nothing++-- | A read- and writable property on a UD object.+data Property e a = Property+  { propertyGet :: a -> LuaE e NumResults+  , propertySet :: Maybe (StackIndex -> a -> LuaE e a)+  , propertyDescription :: Text+  , propertyType :: TypeSpec+  }++-- | Alias for a different property of this or of a nested object.+type Alias = [AliasIndex]++-- | Index types allowed in aliases (strings and integers)+data AliasIndex+  = StringIndex Name+  | IntegerIndex Lua.Integer+  deriving (Eq, Ord)++instance IsString AliasIndex where+  fromString = StringIndex . fromString++-- | A type member, either a method or a variable.+data Member e fn a+  = MemberProperty Name (Property e a)+  | MemberMethod Name fn+  | MemberAlias AliasIndex Alias++-- | Use a documented function as an object method.+methodGeneric :: Name -> fn -> Member e fn a+methodGeneric = MemberMethod++-- | A property or method which may be available in some instances but+-- not in others.+data Possible a+  = Actual a+  | Absent++-- | Declares a new read- and writable typed property.+property' :: LuaError e+          => Name                       -- ^ property name+          -> TypeSpec                   -- ^ property type+          -> Text                       -- ^ property description+          -> (Pusher e b, a -> b)       -- ^ how to get the property value+          -> (Peeker e b, a -> b -> a)  -- ^ how to set a new property value+          -> Member e fn a+property' name typespec desc (push, get) (peek, set) =+  possibleProperty' name typespec desc+    (push, Actual . get)+    (peek, \a b -> Actual (set a b))++-- | Declares a new read- and writable property.+property :: LuaError e+         => Name                       -- ^ property name+         -> Text                       -- ^ property description+         -> (Pusher e b, a -> b)       -- ^ how to get the property value+         -> (Peeker e b, a -> b -> a)  -- ^ how to set a new property value+         -> Member e fn a+property name desc (push, get) (peek, set) =+  possibleProperty name desc+    (push, Actual . get)+    (peek, \a b -> Actual (set a b))++-- | Declares a new read- and writable property which is not always+-- available.+possibleProperty :: LuaError e+  => Name                               -- ^ property name+  -> Text                               -- ^ property description+  -> (Pusher e b, a -> Possible b)      -- ^ how to get the property value+  -> (Peeker e b, a -> b -> Possible a) -- ^ how to set a new property value+  -> Member e fn a+possibleProperty name = possibleProperty' name anyType++-- | Declares a new read- and writable property which is not always+-- available.+possibleProperty' :: LuaError e+  => Name                               -- ^ property name+  -> TypeSpec                           -- ^ type of the property value+  -> Text                               -- ^ property description+  -> (Pusher e b, a -> Possible b)      -- ^ how to get the property value+  -> (Peeker e b, a -> b -> Possible a) -- ^ how to set a new property value+  -> Member e fn a+possibleProperty' name typespec desc (push, get) (peek, set) =+  MemberProperty name $+  Property+  { propertyGet = \x -> do+      case get x of+        Actual y -> NumResults 1 <$ push y+        Absent   -> return (NumResults 0)+  , propertySet = Just $ \idx x -> do+      value  <- forcePeek $ peek idx+      case set x value of+        Actual y -> return y+        Absent   -> failLua $ "Trying to set unavailable property "+                            <> Utf8.toString (fromName name)+                            <> "."+  , propertyType = typespec+  , propertyDescription = desc+  }++-- | Creates a read-only object property. Attempts to set the value will+-- cause an error.+readonly' :: Name                 -- ^ property name+          -> TypeSpec             -- ^ property type+          -> Text                 -- ^ property description+          -> (Pusher e b, a -> b) -- ^ how to get the property value+          -> Member e fn a+readonly' name typespec desc (push, get) = MemberProperty name $+  Property+  { propertyGet = \x -> do+      push $ get x+      return (NumResults 1)+  , propertySet = Nothing+  , propertyType = typespec+  , propertyDescription = desc+  }++-- | Creates a read-only object property. Attempts to set the value will+-- cause an error.+readonly :: Name                 -- ^ property name+         -> Text                 -- ^ property description+         -> (Pusher e b, a -> b) -- ^ how to get the property value+         -> Member e fn a+readonly name = readonly' name anyType++-- | Define an alias for another, possibly nested, property.+alias :: AliasIndex    -- ^ property alias+      -> Text          -- ^ description+      -> [AliasIndex]  -- ^ sequence of nested properties+      -> Member e fn a+alias name _desc = MemberAlias name++-- | Ensures that the type has been fully initialized, i.e., that all+-- metatables have been created and stored in the registry. Returns the+-- name of the initialized type.+initType :: LuaError e+         => UDTypeGeneric e fn a+         -> LuaE e Name+initType ty = do+  pushUDMetatable ty+  pop 1+  return (udName ty)++-- | Pushes the metatable for the given type to the Lua stack. Creates+-- the new table afresh on the first time it is needed, and retrieves it+-- from the registry after that.+--+--+-- A @hook@ can be used to perform additional setup operations. The+-- function is called as the last step after the type metatable has been+-- initialized: the fully initialized metatable will be at the top of+-- the stack at that point. Note that the hook will /not/ be called if+-- the type's metatable already existed before this function was+-- invoked.+pushUDMetatable+  :: forall e fn a. LuaError e+  => UDTypeGeneric e fn a+  -> LuaE e ()+pushUDMetatable ty = do+  created <- newudmetatable (udName ty)+  when created $ do+    add (metamethodName Index)    $ pushcfunction hslua_udindex_ptr+    add (metamethodName Newindex) $ pushcfunction hslua_udnewindex_ptr+    add (metamethodName Pairs)    $ pushHaskellFunction (pairsFunction ty)+    forM_ (udOperations ty) $ \(op, f) -> do+      add (metamethodName op) $ udFnPusher ty f+    add "getters" $ pushGetters ty+    add "setters" $ pushSetters ty+    add "methods" $ pushMethods ty+    add "aliases" $ pushAliases ty+    hookMetatableSetup (udHooks ty)+  where+    add :: Name -> LuaE e () -> LuaE e ()+    add name op = do+      pushName name+      op+      rawset (nth 3)++-- | Retrieves a key from a Haskell-data holding userdata value.+--+-- Does 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;+--+--   - Looks up the key in the table in the @methods@ metafield.+foreign import ccall "hslobj.c &hslua_udindex"+  hslua_udindex_ptr :: FunPtr (State -> IO NumResults)++-- | 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.+foreign import ccall "hslobj.c &hslua_udnewindex"+  hslua_udnewindex_ptr :: FunPtr (State -> IO NumResults)++-- | Sets a value in the userdata's caching table (uservalue). Takes the+-- same arguments as a @__newindex@ function.+foreign import ccall "hslobj.c &hslua_udsetter"+  hslua_udsetter_ptr :: FunPtr (State -> IO NumResults)++-- | Throws an error nothing that the given key is read-only.+foreign import ccall "hslobj.c &hslua_udreadonly"+  hslua_udreadonly_ptr :: FunPtr (State -> IO NumResults)++-- | Pushes the metatable's @getters@ field table.+pushGetters+  :: LuaError e+  => UDTypeGeneric e fn a -> LuaE e ()+pushGetters ty = do+  newtable+  void $ flip Map.traverseWithKey (udProperties ty) $ \name prop -> do+    pushName name+    pushHaskellFunction $ forcePeek (peekUDGeneric ty 1) >>= propertyGet prop+    rawset (nth 3)++-- | Pushes the metatable's @setters@ field table.+pushSetters :: LuaError e => UDTypeGeneric e fn a -> LuaE e ()+pushSetters ty = do+  newtable+  void $ flip Map.traverseWithKey (udProperties ty) $ \name prop -> do+    pushName name+    pushcfunction $ case propertySet prop of+      Just _  -> hslua_udsetter_ptr+      Nothing -> hslua_udreadonly_ptr+    rawset (nth 3)++-- | Pushes the metatable's @methods@ field table.+pushMethods :: LuaError e => UDTypeGeneric e fn a -> LuaE e ()+pushMethods ty = do+  newtable+  void $ flip Map.traverseWithKey (udMethods ty) $ \name fn -> do+    pushName name+    udFnPusher ty fn+    rawset (nth 3)++pushAliases :: LuaError e => UDTypeGeneric e fn a -> LuaE e ()+pushAliases ty = do+  newtable+  void $ flip Map.traverseWithKey (udAliases ty) $ \name propSeq -> do+    pushAliasIndex name+    pushList pushAliasIndex propSeq+    rawset (nth 3)++pushAliasIndex :: Pusher e AliasIndex+pushAliasIndex = \case+  StringIndex name -> pushName name+  IntegerIndex n   -> pushIntegral n++-- | Pushes the function used to iterate over the object's key-value+-- pairs in a generic *for* loop.+pairsFunction+  :: LuaError err+  => UDTypeGeneric err fn a -> LuaE err NumResults+pairsFunction ty = do+  obj <- forcePeek $ peekUDGeneric ty (nthBottom 1)+  let pushMember = \case+        MemberProperty name prop -> do+          pushName name+          getresults <- propertyGet prop obj+          if getresults == 0+            then 0 <$ pop 1  -- property is absent, don't push anything+            else return $ getresults + 1+        MemberMethod name f -> do+          pushName name+          udFnPusher ty f+          return 2+        MemberAlias{} -> fail "aliases are not full properties"+  pushIterator pushMember $+    map (uncurry MemberProperty) (Map.toAscList (udProperties ty)) +++    map (uncurry MemberMethod) (Map.toAscList (udMethods ty))++-- | Pushes a userdata value of the given type.+pushUDGeneric+  :: LuaError e+  => UDTypeGeneric e fn a                -- ^ userdata type+  -> a                                   -- ^ value to push+  -> LuaE e ()+pushUDGeneric ty x = do+  newhsuserdatauv x (hookUservalues (udHooks ty))+  pushUDMetatable ty+  setmetatable (nth 2)+  hookPushUD (udHooks ty) x++-- | Retrieves a userdata value of the given type.+peekUDGeneric :: LuaError e+              => UDTypeGeneric e fn a -> Peeker e a+peekUDGeneric ty idx = do+  let name = udName ty+  old <- reportValueOnFailure name (`fromuserdata` name) idx+  -- get caching table and update the Haskell value+  updated <- liftLua (getiuservalue idx 1) >>= \case+    TypeTable -> liftLua $ do+      pushnil+      setProperties (udProperties ty) old+    _other -> return old+  liftLua $ pop 1  -- pop caching table+  hookPeekUD (udHooks ty) updated idx++-- | Retrieves object properties from a uservalue table and sets them on+-- the given value. Expects the uservalue table at the top of the stack.+setProperties :: LuaError e => Map Name (Property e a) -> a -> LuaE e a+setProperties props x = do+  hasNext <- Unsafe.next (nth 2)+  let continue value = pop 1 *> setProperties props value+  if not hasNext+    then return x+    else ltype (nth 2) >>= \case+      TypeString -> do+        propName <- forcePeek $ peekName (nth 2)+        case Map.lookup propName props >>= propertySet of+          Nothing -> continue x+          Just setter -> do+            x' <- setter top x+            continue x'+      _ -> continue x++--+-- Typing+--++-- | Returns documentation for this type.+udDocs :: UDTypeGeneric e fn a+       -> TypeDocs+udDocs ty = TypeDocs+  { typeDescription = mempty+  , typeSpec = userdataType+  , typeRegistry = Just (udName ty)+  }++-- | Type specifier for a UDType+udTypeSpec :: UDTypeGeneric e fn a+           -> TypeSpec+udTypeSpec = NamedType . udName
+ src/HsLua/ObjectOrientation/ListType.hs view
@@ -0,0 +1,166 @@+{-# LANGUAGE FlexibleInstances        #-}+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE MultiParamTypeClasses    #-}+{-# LANGUAGE OverloadedStrings        #-}+{-# LANGUAGE ScopedTypeVariables      #-}+{-# LANGUAGE TypeApplications         #-}+module HsLua.ObjectOrientation.ListType+  ( UDTypeWithList+  , ListSpec+  , listExtension+  ) where++import Control.Monad ((<$!>), forM_, void)+import Foreign.Ptr (FunPtr)+import HsLua.Core as Lua+import HsLua.Marshalling+import HsLua.ObjectOrientation.Generic+import HsLua.ObjectOrientation.Operation (metamethodName)++-- | Userdata type that (also) behaves like a list.+type UDTypeWithList e fn a itemtype =+  UDTypeGeneric e fn a+{-# DEPRECATED UDTypeWithList "Use UDTypeGeneric instead" #-}++-- | Pair of pairs, describing how a type can be used as a Lua list. The+-- first pair describes how to push the list items, and how the list is+-- extracted from the type; the second pair contains a method to+-- retrieve list items, and defines how the list is used to create an+-- updated value.+type ListSpec e a itemtype =+  ( (Pusher e itemtype, a -> [itemtype])+  , (Peeker e itemtype, a -> [itemtype] -> a)+  )++listExtension+  :: LuaError e+  => ListSpec e a itemtype+  -> UDTypeHooks e fn a+listExtension ((pushItem, toList), (peekItem, updateList)) =+  UDTypeHooks+  { hookMetatableSetup = do+      -- Add a function to evaluate the necessary parts of a lazy list.+      pushName "lazylisteval"+      pushHaskellFunction (lazylisteval pushItem)+      rawset (nth 3)+      -- Use different field getter+      pushName (metamethodName Index)+      pushcfunction hslua_list_udindex_ptr+      rawset (nth 3)+      -- Use different field setter+      pushName (metamethodName Newindex)+      pushcfunction hslua_list_udnewindex_ptr+      rawset (nth 3)++  , hookPeekUD = \x idx ->+    (`lastly` pop 1) $ liftLua (getiuservalue idx 1) >>= \case+      TypeTable -> setList peekItem updateList x+      _other    -> pure x++  , hookPushUD = \x -> do+      -- Add a field containing the thunk with the unevaluated part of+      -- the lazy list.+      newtable+      pushName "__lazylist"+      newhsuserdatauv (toList x) 1+      void (newudmetatable lazyListStateName)+      setmetatable (nth 2)+      rawset (nth 3)+      void (setiuservalue (nth 2) 1)++  , hookUservalues = 1+  }+++-- | Evaluate part of a lazy list. Takes the following arguments, in+-- this order:+--+-- 1. userdata wrapping the unevalled part of the lazy list+-- 2. index of the last evaluated element+-- 3. index of the requested element+-- 4. the caching table+lazylisteval :: forall itemtype e. LuaError e+             => Pusher e itemtype -> LuaE e NumResults+lazylisteval pushItem = do+  munevaled <- fromuserdata @[itemtype] (nthBottom 1) lazyListStateName+  mcurindex <- tointeger (nthBottom 2)+  mnewindex <- tointeger (nthBottom 3)+  case (munevaled, mcurindex, mnewindex) of+    (Just unevaled, Just curindex, Just newindex) -> do+      let numElems = fromIntegral $ max (newindex - curindex) 0+          (as, rest) = splitAt numElems unevaled+      if null rest+        then do+          -- no more elements in list; unset variable+          pushName "__lazylistindex"+          pushBool False+          rawset (nthBottom 4)+        else do+          -- put back remaining unevalled list+          void $ putuserdata @[itemtype] (nthBottom 1) lazyListStateName rest+          pushName "__lazylistindex"+          pushinteger (curindex + fromIntegral (length as))+          rawset (nthBottom 4)+      -- push evaluated elements+      forM_ (zip [(curindex + 1)..] as) $ \(i, a) -> do+        pushItem a+        rawseti (nthBottom 4) i+      return (NumResults 0)+    _ -> pure (NumResults 0)++-- | Name of the metatable used for unevaluated lazy list rema+lazyListStateName :: Name+lazyListStateName = "HsLua unevalled lazy list"++-- | Gets a list from a uservalue table and sets it on the given value.+-- Expects the uservalue (i.e., caching) table to be at the top of the+-- stack.+setList :: forall itemtype a e. LuaError e+        => Peeker e itemtype+        -> (a -> [itemtype] -> a)+        -> a+        -> Peek e a+setList peekItem updateList x = (x `updateList`) <$!> do+  liftLua (getfield top "__lazylistindex") >>= \case+    TypeBoolean -> do+      -- list had been fully evaluated+      liftLua $ pop 1+      peekList peekItem top+    _ -> do+      let getLazyList = do+            liftLua (getfield top "__lazylist") >>= \case+              TypeUserdata -> pure ()+              otherType -> do+                tyname <- liftLua $ typename otherType+                failPeek $+                  "unevaled items of lazy list cannot be peeked: got " <>+                  tyname+            (`lastly` pop 1) $ reportValueOnFailure+              lazyListStateName+              (\idx -> fromuserdata @[itemtype] idx lazyListStateName)+              top+      mlastIndex <- liftLua (tointeger top <* pop 1)+      let itemsAfter = case mlastIndex of+            Nothing -> const getLazyList+            Just lastIndex -> \i ->+              if i <= lastIndex+              then liftLua (rawgeti top i) >>= \case+                TypeNil -> [] <$ liftLua (pop 1)+                _ -> do+                  y <- peekItem top `lastly` pop 1+                  (y:) <$!> itemsAfter (i + 1)+              else getLazyList+      itemsAfter 1+++-- | Gets a new value in the userdata caching table via a getter+-- functions; this function differs from the normal getter in that it+-- treats numerical values as list indices.+foreign import ccall "hslobj.c &hslua_list_udindex"+  hslua_list_udindex_ptr :: FunPtr (State -> IO NumResults)++-- | Sets a new value in the userdata caching table via a setter+-- functions; this function differs from the normal setter in that it+-- treats numerical values as list indices.+foreign import ccall "hslobj.c &hslua_list_udnewindex"+  hslua_list_udnewindex_ptr :: FunPtr (State -> IO NumResults)
test/HsLua/ObjectOrientationTests.hs view
@@ -173,7 +173,7 @@     [ "type table is added to the registry" =:       TypeTable `shouldBeResultOf` do         openlibs-        name <- initTypeGeneric (\_ -> pure ()) typeBar+        name <- initType typeBar         getfield registryindex name      , "type table is not in registry when uninitialized" =:@@ -185,7 +185,7 @@       0 `shouldBeResultOf` do         openlibs         before <- gettop-        _ <- initTypeGeneric (\_ -> pure ()) typeBar+        _ <- initType typeBar         after <- gettop         return $ after - before     ]@@ -274,7 +274,7 @@     , "Infinite lists are ok" =:       233 `shouldBeResultOf` do         openlibs-        let fibs = 0 : 1 : zipWith (+) fibs (tail fibs)+        let fibs = 0 : 1 : zipWith (+) fibs (drop 1 fibs)         pushUD typeLazyIntList $ LazyIntList fibs         setglobal "fibs"         dostring "return fibs[14]" >>= \case@@ -373,6 +373,7 @@     ]   ] +-- | Define a default UDType without bells and whistles. deftype :: LuaError e         => Name                              -- ^ type name         -> [(Operation, HaskellFunction e)]  -- ^ operations@@ -380,17 +381,11 @@         -> UDType e (HaskellFunction e) a deftype = deftypeGeneric pushHaskellFunction -deftype' :: LuaError e-         => Name                  -- ^ type name-         -> [(Operation, HaskellFunction e)]  -- ^ operations-         -> [Member e (HaskellFunction e) a]  -- ^ methods-         -> Maybe (ListSpec e a itemtype)  -- ^ list access-         -> UDTypeWithList e (HaskellFunction e) a itemtype-deftype' = deftypeGeneric' pushHaskellFunction- -- | Pushes a userdata value of the given type.-pushUD :: LuaError e => UDTypeWithList e fn a itemtype -> a -> LuaE e ()-pushUD = pushUDGeneric (const (pure ()))+pushUD+  :: LuaError e+  => UDTypeGeneric e fn a -> a -> LuaE e ()+pushUD = pushUDGeneric  -- | Define a (meta) operation on a type. operation :: Operation -> HaskellFunction e -> (Operation, HaskellFunction e)@@ -432,17 +427,18 @@   deriving (Eq, Show)  typeLazyIntList :: LuaError e-                => UDTypeWithList e (HaskellFunction e) LazyIntList Int-typeLazyIntList = deftype' "LazyIntList"+                => UDTypeGeneric e (HaskellFunction e) LazyIntList+typeLazyIntList = deftypeGeneric' pushHaskellFunction "LazyIntList"   [ operation Tostring $ do       lazyList <- forcePeek $ peekUDGeneric typeLazyIntList (nthBottom 1)       pushString (show lazyList)       return (NumResults 1)   ]   [ alias "seq" "sequence" [] ]-  (Just ( (pushIntegral, fromLazyIntList)-        , (peekIntegral, \_ lst -> LazyIntList lst)-        ))+  (listExtension+    ( (pushIntegral, fromLazyIntList)+    , (peekIntegral, \_ lst -> LazyIntList lst)+    ))  -- -- Sample sum type