diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,74 +1,116 @@
 # Changelog
 
-`lua` uses [PVP Versioning][1].
+`lua` uses [PVP Versioning][].
 
-## lua 2.0.2
+## lua-2.1.0
 
+Released 29-01-2022.
+
+-   The functions `lua_rawget` and `lua_rawgeti` now return the
+    type of the value that was pushed to the stack.
+
+-   Added bindings to unsafe function `lua_arith`. A new type
+    `ArithOPCode` for arithmetic operations is added, as are
+    pattern synonyms for the supported operations. These are:
+
+    -   LUA_OPADD
+    -   LUA_OPSUB
+    -   LUA_OPMUL
+    -   LUA_OPDIV
+    -   LUA_OPIDIV
+    -   LUA_OPMOD
+    -   LUA_OPPOW
+    -   LUA_OPUNM
+    -   LUA_OPBNOT
+    -   LUA_OPBAND
+    -   LUA_OPBOR
+    -   LUA_OPBXOR
+    -   LUA_OPSHL
+    -   LUA_OPSHR
+
+-   Added ersatz functions `hslua_arith` and `hsluaL_requiref`,
+    wrapping `lua_artih` and `luaL_requiref`, respectively. Both
+    functions catch any error resulting from the call.
+
+-   Made types `OPCode`, `StatusCode`, and `GCCode` instances of
+    type class `Show`.
+
+-   The `Show` instances of Integer and Number now behave like
+    those of the wrapped types. Both types are now also instance
+    of `Read`.
+
+-   Removed `hslua_userdata_gc` from `hslua.h`.
+
+## lua-2.0.2
+
 Released 2021-11-26.
 
-- Make sure lualib.h is available through this package. The header
-  file contains info on how and under which name the standard
-  library is loaded.
+-   Make sure lualib.h is available through this package. The
+    header file contains info on how and under which name the
+    standard library is loaded.
 
-## lua 2.0.1
+## lua-2.0.1
 
 Released 2021-11-03.
 
-- Added bindings to `lua_rotate` and `lua_version`.
+-   Added bindings to `lua_rotate` and `lua_version`.
 
-## lua 2.0.0.1
+## lua-2.0.0.1
 
 Released 2021-10-30.
 
-- Only install includes when using the Lua code shipped with the package
-  (Ellie Hermaszewska). Cabal no longer tries to install the header
-  files if a system-wide installation is used.
+-   Only install includes when using the Lua code shipped with the
+    package (Ellie Hermaszewska). Cabal no longer tries to install
+    the header files if a system-wide installation is used.
 
-## lua 2.0.0
+## lua-2.0.0
 
-Release 2021-10-21.
+Released 2021-10-21.
 
-- Module hierarchy moved from `Foreign.Lua.Raw` to `Lua`.
+-   Module hierarchy moved from `Foreign.Lua.Raw` to `Lua`.
 
-- Documentation has been improved.
+-   Documentation has been improved.
 
-- Added new function `withNewState` to run Lua operations.
+-   Added new function `withNewState` to run Lua operations.
 
-- New modules `Lua.Ersatz` containing all bindings to safe
-  ersatz functions.
+-   New modules `Lua.Ersatz` containing all bindings to safe
+    ersatz functions.
 
-- Higher level and enum types have been removed, only the
-  low-level "code" types are kept in this package.
+-   Higher level and enum types have been removed, only the
+    low-level “code” types are kept in this package.
 
-- Constants are now represented as pattern synonyms like `LUA_OK`.
+-   Constants are now represented as pattern synonyms like
+    `LUA_OK`.
 
-- Provide bindings to more functions:
-    + `lua_is...` type-checking functions;
-    + `lua_pushstring` to push plain CStrings;
-    + auxiliary functions
-        * `luaL_loadfile`, and
-        * `luaL_loadfilex`;
-    + unsafe functions
-        * `lua_gettable`,
-        * `lua_settable`,
-        * `lua_getglobal`, and
-        * `lua_setglobal`.
+-   Provide bindings to more functions:
 
-- The function `lua_pop` now expects a `CInt` instead of a
-  `StackIndex`.
+    -   `lua_is...` type-checking functions;
+    -   `lua_pushstring` to push plain CStrings;
+    -   auxiliary functions
+        -   `luaL_loadfile`, and
+        -   `luaL_loadfilex`;
+    -   unsafe functions
+        -   `lua_gettable`,
+        -   `lua_settable`,
+        -   `lua_getglobal`, and
+        -   `lua_setglobal`.
 
-- New StackIndex constructor functions `nthTop`, `nthBottom`,
-  `nth`, and `top`.
+-   The function `lua_pop` now expects a `CInt` instead of a
+    `StackIndex`.
 
-- Avoid unnecessary modification of HSFUN metatable.
+-   New StackIndex constructor functions `nthTop`, `nthBottom`,
+    `nth`, and `top`.
 
-- Various cleanups and test improvements.
+-   Avoid unnecessary modification of HSFUN metatable.
 
-## lua 1.0.0
+-   Various cleanups and test improvements.
 
+## lua-1.0.0
+
 Released 2021-02-18.
 
-- Initially created. Contains all modules in the `Foreign.Lua.Raw`
-  hierarchy from `hslua-1.3`. Documentation has been improved.
+-   Initially created. Contains all modules in the
+    `Foreign.Lua.Raw` hierarchy from `hslua-1.3`. Documentation
+    has been improved.
 
-[1]: https://pvp.haskell.org
+  [PVP Versioning]: https://pvp.haskell.org
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,7 @@
 Copyright © 1994-2020 Lua.org, PUC-Rio.
 Copyright © 2007-2012 Gracjan Polak
 Copyright © 2012-2015 Ömer Sinan Ağacan
-Copyright © 2016-2021 Albert Krewinkel
+Copyright © 2016-2022 Albert Krewinkel
 
 Permission is hereby granted, free of charge, to any person obtaining a
 copy of this software and associated documentation files (the
diff --git a/cbits/hslua/hslauxlib.c b/cbits/hslua/hslauxlib.c
--- a/cbits/hslua/hslauxlib.c
+++ b/cbits/hslua/hslauxlib.c
@@ -28,7 +28,7 @@
 /*
 ** Helper for hsluaL_tostring
 */
-int hsluaL__tolstring(lua_State *L)
+static int hsluaL__tolstring(lua_State *L)
 {
   luaL_tolstring(L, 1, NULL);
   return 1;
@@ -49,4 +49,28 @@
     return NULL;
   }
   return lua_tolstring(L, -1, len);
+}
+
+static int auxrequiref(lua_State *L)
+{
+  const char *modname = lua_tolstring(L, 1, NULL);
+  lua_CFunction openf = lua_tocfunction(L, 2);
+  int glb = lua_toboolean(L, 3);
+  luaL_requiref(L, modname, openf, glb);
+  return 1;
+}
+
+/*
+** Simple version of `require` used to load modules from a C function.
+*/
+void hsluaL_requiref (lua_State *L, const char *modname,
+                      lua_CFunction openf, int glb, int *status)
+{
+  lua_pushcfunction(L, &auxrequiref);
+  lua_pushstring(L, modname);
+  lua_pushcfunction(L, openf);
+  lua_pushboolean(L, glb);
+  int pstatus = lua_pcall(L, 3, 1, 0);
+  if (status != NULL)
+    *status = pstatus;
 }
diff --git a/cbits/hslua/hslcall.c b/cbits/hslua/hslcall.c
--- a/cbits/hslua/hslcall.c
+++ b/cbits/hslua/hslcall.c
@@ -8,7 +8,7 @@
 /* ***************************************************************
  * Transforming Haskell errors to Lua errors
  * ***************************************************************/
-void hslua_pushhaskellerr(lua_State *L)
+static void hslua_pushhaskellerr(lua_State *L)
 {
   lua_getfield(L, LUA_REGISTRYINDEX, HSLUA_ERR);
 }
@@ -27,7 +27,7 @@
 /*
 ** Checks whether the object at the given index is a Haskell error.
 */
-int hslua_is_haskell_error(lua_State *L, int idx)
+static int hslua_is_haskell_error(lua_State *L, int idx)
 {
   int erridx = lua_absindex(L, idx);
   hslua_pushhaskellerr(L);
diff --git a/cbits/hslua/hslua.c b/cbits/hslua/hslua.c
--- a/cbits/hslua/hslua.c
+++ b/cbits/hslua/hslua.c
@@ -9,9 +9,37 @@
  * ***************************************************************/
 
 /*
+** arith auxiliary function.
+*/
+static int auxhslua_arith(lua_State *L)
+{
+  int op = lua_tointeger(L, -1);
+  lua_pop(L, 1);
+  lua_arith(L, op);
+  return 1;
+}
+
+/*
+** Compares two values on the stack. Behaves mostly like lua_compare,
+** but takes an additional parameter `status`, the referent of which is
+** assigned the result of calling the helper function.
+*/
+void hslua_arith(lua_State *L, int op, int *status)
+{
+  int nops = op == LUA_OPUNM ? 1 : 2;
+  lua_pushcfunction(L, auxhslua_arith);
+  lua_insert(L, -(nops + 1));
+  lua_pushinteger(L, op);
+  int outcome = lua_pcall(L, nops + 1, 1, 0);
+  if (status != NULL) {
+    *status = outcome;
+  }
+}
+
+/*
 ** compare
 */
-int hslua__compare(lua_State *L)
+static int hslua__compare(lua_State *L)
 {
   int op = lua_tointeger(L, 3);
   int res = lua_compare(L, 1, 2, op);
@@ -48,7 +76,7 @@
 /*
 ** concat
 */
-int hslua__concat(lua_State *L)
+static int hslua__concat(lua_State *L)
 {
   lua_concat(L, lua_gettop(L));
   return 1;
@@ -65,7 +93,7 @@
 /*
 ** getglobal
 */
-int hslua__getglobal(lua_State *L)
+static int hslua__getglobal(lua_State *L)
 {
   lua_gettable(L, 1);
   return 1;
@@ -88,7 +116,7 @@
 /*
 ** gettable
 */
-int hslua__gettable(lua_State *L)
+static int hslua__gettable(lua_State *L)
 {
   lua_pushvalue(L, 1);
   lua_gettable(L, 2);
@@ -112,7 +140,7 @@
 /*
 ** setglobal
 */
-int hslua__setglobal(lua_State *L)
+static int hslua__setglobal(lua_State *L)
 {
   /* index 1: value */
   /* index 2: the global table */
@@ -140,7 +168,7 @@
 /*
 ** settable
 */
-int hslua__settable(lua_State *L)
+static int hslua__settable(lua_State *L)
 {
   lua_pushvalue(L, 1); /* key */
   lua_pushvalue(L, 2); /* value */
@@ -160,7 +188,7 @@
 /*
 ** next
 */
-int hslua__next(lua_State *L)
+static int hslua__next(lua_State *L)
 {
   lua_pushvalue(L, 1);
   return lua_next(L, 2) ? 2 : 0;
diff --git a/cbits/hslua/hslua.h b/cbits/hslua/hslua.h
--- a/cbits/hslua/hslua.h
+++ b/cbits/hslua/hslua.h
@@ -1,21 +1,17 @@
 #include <lua.h>
 
+/* Replacement for lua_error, signals an error in a function */
 int hslua_error(lua_State *L);
 
+/* Converts a Haskell function into a lua_CFunction. */
 int hslua_call_hs(lua_State *L);
 
-int hslua_userdata_gc(lua_State *L);
-
+/* Ersatz functions */
+void hslua_arith(lua_State *L, int op, int *status);
 int hslua_compare(lua_State *L, int index1, int index2, int op, int *status);
-
 void hslua_concat(lua_State *L, int n, int *status);
-
 int hslua_getglobal(lua_State *L, const char *name, size_t len, int *status);
-
 int hslua_gettable(lua_State *L, int index, int *status);
-
+int hslua_next(lua_State *L, int index, int *status);
 void hslua_setglobal(lua_State *L, const char *k, size_t len, int *status);
-
 void hslua_settable(lua_State *L, int index, int *status);
-
-int hslua_next(lua_State *L, int index, int *status);
diff --git a/cbits/hslua/hsludata.c b/cbits/hslua/hsludata.c
--- a/cbits/hslua/hsludata.c
+++ b/cbits/hslua/hsludata.c
@@ -12,7 +12,7 @@
 ** The userdata whose contents is garbage collected must be on
 ** stack index 1 (i.e., the first argument).
 */
-int hslua_userdata_gc(lua_State *L)
+static int hslua_userdata_gc(lua_State *L)
 {
   HsStablePtr *userdata = lua_touserdata(L, 1);
   if (userdata) {
diff --git a/lua.cabal b/lua.cabal
--- a/lua.cabal
+++ b/lua.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.2
 name:                lua
-version:             2.0.2
+version:             2.1.0
 synopsis:            Lua, an embeddable scripting language
 description:         This package provides bindings and types to bridge
                      Haskell and <https://www.lua.org/ Lua>.
@@ -16,7 +16,7 @@
 maintainer:          Albert Krewinkel <albert+hslua@zeitkraut.de>
 copyright:           © 2007–2012 Gracjan Polak;
                      © 2012–2016 Ömer Sinan Ağacan;
-                     © 2017-2021 Albert Krewinkel
+                     © 2017-2022 Albert Krewinkel
 category:            Foreign
 build-type:          Simple
 extra-source-files:  cbits/lua-5.3.6/*.h
@@ -28,8 +28,9 @@
                    , GHC == 8.4.4
                    , GHC == 8.6.5
                    , GHC == 8.8.4
-                   , GHC == 8.10.4
+                   , GHC == 8.10.7
                    , GHC == 9.0.1
+                   , GHC == 9.2.1
 
 source-repository head
   type:                git
@@ -132,6 +133,7 @@
   default-extensions:  CApiFFI
                      , ForeignFunctionInterface
                      , LambdaCase
+                     , StrictData
   other-extensions:    CPP
                      , DeriveGeneric
                      , GeneralizedNewtypeDeriving
@@ -210,6 +212,7 @@
   main-is:             test-lua.hs
   hs-source-dirs:      test
   other-modules:       Lua.ErsatzTests
+                     , Lua.PrimaryTests
                      , Lua.UnsafeTests
   build-depends:       lua
                      , tasty                >= 0.11
diff --git a/src/Lua.hs b/src/Lua.hs
--- a/src/Lua.hs
+++ b/src/Lua.hs
@@ -2,7 +2,7 @@
 {-# LANGUAGE PatternSynonyms #-}
 {-|
 Module      : Lua
-Copyright   : © 2021 Albert Krewinkel
+Copyright   : © 2021-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
@@ -73,8 +73,23 @@
   , pattern LUA_OPEQ
   , pattern LUA_OPLT
   , pattern LUA_OPLE
-  , StatusCode (..)
+    -- ** Codes for arithmetic operations
+  , pattern LUA_OPADD
+  , pattern LUA_OPSUB
+  , pattern LUA_OPMUL
+  , pattern LUA_OPDIV
+  , pattern LUA_OPIDIV
+  , pattern LUA_OPMOD
+  , pattern LUA_OPPOW
+  , pattern LUA_OPUNM
+  , pattern LUA_OPBNOT
+  , pattern LUA_OPBAND
+  , pattern LUA_OPBOR
+  , pattern LUA_OPBXOR
+  , pattern LUA_OPSHL
+  , pattern LUA_OPSHR
     -- ** Status codes
+  , StatusCode (..)
   , pattern LUA_OK
   , pattern LUA_YIELD
   , pattern LUA_ERRRUN
@@ -139,6 +154,7 @@
   , lua_pushlstring
   , lua_pushstring
   , lua_pushcclosure
+  , lua_pushcfunction
   , lua_pushboolean
   , lua_pushlightuserdata
   , lua_pushthread
@@ -158,7 +174,10 @@
   , lua_setuservalue
   , lua_setglobal
   , lua_settable
+    -- ** Misc (safe)
+  , lua_stringtonumber
     -- ** Misc (unsafe)
+  , lua_arith
   , lua_concat
   , lua_next
     -- ** Load and run Lua code
@@ -204,6 +223,7 @@
     -- * Ersatz functions
   , hsluaL_newstate
   , hsluaL_tolstring
+  , hsluaL_requiref
     -- ** Get functions (Lua → stack)
   , hslua_gettable
   , hslua_getglobal
@@ -214,6 +234,7 @@
   , hslua_error
   , hslua_next
   , hslua_concat
+  , hslua_arith
   , hslua_compare
 
     -- * Standard Lua libraries
diff --git a/src/Lua/Auxiliary.hs b/src/Lua/Auxiliary.hs
--- a/src/Lua/Auxiliary.hs
+++ b/src/Lua/Auxiliary.hs
@@ -3,7 +3,7 @@
 Module      : Lua.Auxiliary
 Copyright   : © 2007–2012 Gracjan Polak;
               © 2012–2016 Ömer Sinan Ağacan;
-              © 2017-2021 Albert Krewinkel
+              © 2017-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
@@ -138,13 +138,13 @@
 --
 -- In both cases pushes onto the stack the final value associated with
 -- @tname@ in the registry.
-foreign import ccall SAFTY "lauxlib.h luaL_newmetatable"
+foreign import capi SAFTY "lauxlib.h luaL_newmetatable"
   luaL_newmetatable :: Lua.State -> CString {- ^ tname -} -> IO Lua.LuaBool
 
 -- | Opens all standard Lua libraries into the given state.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#luaL_openlibs>
-foreign import ccall unsafe "lualib.h luaL_openlibs"
+foreign import capi unsafe "lualib.h luaL_openlibs"
   luaL_openlibs :: Lua.State -> IO ()
 
 -- | Creates and returns a reference, in the table at index @t@, for the
@@ -160,7 +160,7 @@
 -- constant @'Lua.Constants.LUA_REFNIL'@. The constant
 -- @'Lua.Constants.LUA_NOREF'@ is guaranteed to be different
 -- from any reference returned by @luaL_ref@.
-foreign import ccall SAFTY "lauxlib.h luaL_ref"
+foreign import capi SAFTY "lauxlib.h luaL_ref"
   luaL_ref :: Lua.State -> StackIndex {- ^ t -} -> IO CInt
 
 -- | Creates and pushes a traceback of the stack @l1@. If @msg@ is not
@@ -177,7 +177,7 @@
 -- @'luaL_ref'@). The entry is removed from the table, so that the
 -- referred object can be collected. The reference @ref@ is also freed
 -- to be used again.
-foreign import ccall SAFTY "lauxlib.h luaL_unref"
+foreign import capi SAFTY "lauxlib.h luaL_unref"
   luaL_unref :: Lua.State -> StackIndex {- ^ t -} -> CInt {- ^ ref -} -> IO ()
 
 -- | Checks whether the function argument @arg@ is a userdata of the
diff --git a/src/Lua/Call.hs b/src/Lua/Call.hs
--- a/src/Lua/Call.hs
+++ b/src/Lua/Call.hs
@@ -3,7 +3,7 @@
 Module      : Lua.Call
 Copyright   : © 2007–2012 Gracjan Polak;
               © 2012–2016 Ömer Sinan Ağacan;
-              © 2017-2021 Albert Krewinkel
+              © 2017-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
diff --git a/src/Lua/Constants.hsc b/src/Lua/Constants.hsc
--- a/src/Lua/Constants.hsc
+++ b/src/Lua/Constants.hsc
@@ -3,7 +3,7 @@
 Module      : Lua.Constants
 Copyright   : © 2007–2012 Gracjan Polak;
               © 2012–2016 Ömer Sinan Ağacan;
-              © 2017-2021 Albert Krewinkel
+              © 2017-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
@@ -40,6 +40,21 @@
   , pattern LUA_OPEQ
   , pattern LUA_OPLT
   , pattern LUA_OPLE
+    -- * Codes for arithmetic operations
+  , pattern LUA_OPADD
+  , pattern LUA_OPSUB
+  , pattern LUA_OPMUL
+  , pattern LUA_OPDIV
+  , pattern LUA_OPIDIV
+  , pattern LUA_OPMOD
+  , pattern LUA_OPPOW
+  , pattern LUA_OPUNM
+  , pattern LUA_OPBNOT
+  , pattern LUA_OPBAND
+  , pattern LUA_OPBOR
+  , pattern LUA_OPBXOR
+  , pattern LUA_OPSHL
+  , pattern LUA_OPSHR
     -- * Garbage-collection options
   , pattern LUA_GCSTOP
   , pattern LUA_GCRESTART
@@ -173,6 +188,66 @@
 -- | Compares for less or equal (<=)
 pattern LUA_OPLE :: OPCode
 pattern LUA_OPLE = OPCode #{const LUA_OPLE}
+
+--
+-- Arithmetic and bitwise operators
+--
+
+-- | Performs addition (@+@).
+pattern LUA_OPADD :: ArithOPCode
+pattern LUA_OPADD = ArithOPCode #{const LUA_OPADD}
+
+-- | Performs subtraction (@-@)
+pattern LUA_OPSUB :: ArithOPCode
+pattern LUA_OPSUB = ArithOPCode #{const LUA_OPSUB}
+
+-- | Performs multiplication (@*@)
+pattern LUA_OPMUL :: ArithOPCode
+pattern LUA_OPMUL = ArithOPCode #{const LUA_OPMUL}
+
+-- | Performs float division (@\/@)
+pattern LUA_OPDIV :: ArithOPCode
+pattern LUA_OPDIV = ArithOPCode #{const LUA_OPDIV}
+
+-- | Performs floor division (@\/\/@)
+pattern LUA_OPIDIV :: ArithOPCode
+pattern LUA_OPIDIV = ArithOPCode #{const LUA_OPIDIV}
+
+-- | Performs modulo (@%@)
+pattern LUA_OPMOD :: ArithOPCode
+pattern LUA_OPMOD = ArithOPCode #{const LUA_OPMOD}
+
+-- | Performs exponentiation (@^@)
+pattern LUA_OPPOW :: ArithOPCode
+pattern LUA_OPPOW = ArithOPCode #{const LUA_OPPOW}
+
+-- | Performs mathematical negation (unary @-@)
+pattern LUA_OPUNM :: ArithOPCode
+pattern LUA_OPUNM = ArithOPCode #{const LUA_OPUNM}
+
+-- | Performs bitwise NOT (@~@)
+pattern LUA_OPBNOT :: ArithOPCode
+pattern LUA_OPBNOT = ArithOPCode #{const LUA_OPBNOT}
+
+-- | Performs bitwise AND (@&@)
+pattern LUA_OPBAND :: ArithOPCode
+pattern LUA_OPBAND = ArithOPCode #{const LUA_OPBAND}
+
+-- | Performs bitwise OR (@|@)
+pattern LUA_OPBOR :: ArithOPCode
+pattern LUA_OPBOR = ArithOPCode #{const LUA_OPBOR}
+
+-- | Performs bitwise exclusive OR (@~@)
+pattern LUA_OPBXOR :: ArithOPCode
+pattern LUA_OPBXOR = ArithOPCode #{const LUA_OPBXOR}
+
+-- | Performs left shift (@\<\<@)
+pattern LUA_OPSHL :: ArithOPCode
+pattern LUA_OPSHL = ArithOPCode #{const LUA_OPSHL}
+
+-- | Performs right shift (@>>@)
+pattern LUA_OPSHR :: ArithOPCode
+pattern LUA_OPSHR = ArithOPCode #{const LUA_OPSHR}
 
 --
 -- Garbage-collection options
diff --git a/src/Lua/Ersatz.hs b/src/Lua/Ersatz.hs
--- a/src/Lua/Ersatz.hs
+++ b/src/Lua/Ersatz.hs
@@ -2,7 +2,7 @@
 Module      : Lua.Ersatz
 Copyright   : © 2007–2012 Gracjan Polak;
               © 2012–2016 Ömer Sinan Ağacan;
-              © 2017-2021 Albert Krewinkel
+              © 2017-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
diff --git a/src/Lua/Ersatz/Auxiliary.hs b/src/Lua/Ersatz/Auxiliary.hs
--- a/src/Lua/Ersatz/Auxiliary.hs
+++ b/src/Lua/Ersatz/Auxiliary.hs
@@ -2,7 +2,7 @@
 Module      : Lua.Ersatz.Auxiliary
 Copyright   : © 2007–2012 Gracjan Polak;
               © 2012–2016 Ömer Sinan Ağacan;
-              © 2017-2021 Albert Krewinkel
+              © 2017-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
@@ -13,11 +13,12 @@
 module Lua.Ersatz.Auxiliary
   ( -- * Auxiliary Library
     hsluaL_newstate
+  , hsluaL_requiref
   , hsluaL_tolstring
   ) where
 
 import Foreign.C (CChar, CInt (CInt), CSize)
-import Lua.Types (StackIndex)
+import Lua.Types (CFunction, StackIndex, StatusCode)
 import Foreign.Ptr (Ptr)
 import qualified Lua.Types as Lua
 
@@ -25,6 +26,23 @@
 -- bookkeeping.
 foreign import ccall unsafe "hslauxlib.h hsluaL_newstate"
   hsluaL_newstate :: IO Lua.State
+
+-- | If @modname@ is not already present in @package.loaded@. calls
+-- function @openf@ with string @modname@ as an argument and sets the
+-- call result in @package.loaded[modname]@, as if that function has
+-- been called through
+-- <https://www.lua.org/manual/5.3/manual.html#pdf-require require>.
+--
+-- If @glb@ is true, also stores the module into global @modname@.
+--
+-- Leaves a copy of the module on the stack.
+foreign import ccall unsafe "hslauxlib.c hsluaL_requiref"
+  hsluaL_requiref :: Lua.State
+                  -> Ptr CChar   -- ^ modname
+                  -> CFunction   -- ^ openf
+                  -> Lua.LuaBool -- ^ glb
+                  -> Ptr StatusCode
+                  -> IO ()
 
 -- | Converts object to string, respecting any metamethods; returns
 -- @NULL@ if an error occurs.
diff --git a/src/Lua/Ersatz/Functions.hs b/src/Lua/Ersatz/Functions.hs
--- a/src/Lua/Ersatz/Functions.hs
+++ b/src/Lua/Ersatz/Functions.hs
@@ -3,7 +3,7 @@
 Module      : Lua.Ersatz.Functions
 Copyright   : © 2007–2012 Gracjan Polak;
               © 2012–2016 Ömer Sinan Ağacan;
-              © 2017-2021 Albert Krewinkel
+              © 2017-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
@@ -13,7 +13,8 @@
 throw a Lua error.
 -}
 module Lua.Ersatz.Functions
-  ( hslua_compare
+  ( hslua_arith
+  , hslua_compare
     -- * Get functions (Lua -> stack)
   , hslua_gettable
   , hslua_getglobal
@@ -35,6 +36,39 @@
 #else
 #define SAFTY safe
 #endif
+
+-- | Performs an arithmetic or bitwise operation over the two values (or
+-- one, in the case of negations) at the top of the stack, with the
+-- value at the top being the second operand, pops these values, and
+-- pushes the result of the operation. The function follows the
+-- semantics of the corresponding Lua operator (that is, it may call
+-- metamethods).
+--
+-- The value of @op@ must be one of the following constants:
+--
+-- -   __@LUA_OPADD@:__ performs addition (@+@)
+-- -   __@LUA_OPSUB@:__ performs subtraction (@-@)
+-- -   __@LUA_OPMUL@:__ performs multiplication (@*@)
+-- -   __@LUA_OPDIV@:__ performs float division (@\/@)
+-- -   __@LUA_OPIDIV@:__ performs floor division (@\/\/@)
+-- -   __@LUA_OPMOD@:__ performs modulo (@%@)
+-- -   __@LUA_OPPOW@:__ performs exponentiation (@^@)
+-- -   __@LUA_OPUNM@:__ performs mathematical negation (unary @-@)
+-- -   __@LUA_OPBNOT@:__ performs bitwise NOT (@~@)
+-- -   __@LUA_OPBAND@:__ performs bitwise AND (@&@)
+-- -   __@LUA_OPBOR@:__ performs bitwise OR (@|@)
+-- -   __@LUA_OPBXOR@:__ performs bitwise exclusive OR (@~@)
+-- -   __@LUA_OPSHL@:__ performs left shift (@\<\<@)
+-- -   __@LUA_OPSHR@:__ performs right shift (@>>@)
+--
+-- This function wraps @lua_arith@ and takes an additional parameter
+-- @status@; if it is not @NULL@, then the return value is set to the
+-- status after calling @lua_arith@.
+foreign import ccall SAFTY "hslua.h hslua_arith"
+  hslua_arith :: State
+              -> ArithOPCode     -- ^ op
+              -> Ptr StatusCode
+              -> IO ()
 
 -- | Compares two Lua values. Returns @1@ if the value at index @index1@
 -- satisfies op when compared with the value at index @index2@,
diff --git a/src/Lua/Lib.hs b/src/Lua/Lib.hs
--- a/src/Lua/Lib.hs
+++ b/src/Lua/Lib.hs
@@ -2,7 +2,7 @@
 Module      : Lua.Lib
 Copyright   : © 2007–2012 Gracjan Polak;
               © 2012–2016 Ömer Sinan Ağacan;
-              © 2017-2021 Albert Krewinkel
+              © 2017-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
diff --git a/src/Lua/Primary.hs b/src/Lua/Primary.hs
--- a/src/Lua/Primary.hs
+++ b/src/Lua/Primary.hs
@@ -3,7 +3,7 @@
 Module      : Lua.Primary
 Copyright   : © 2007–2012 Gracjan Polak;
               © 2012–2016 Ömer Sinan Ağacan;
-              © 2017-2021 Albert Krewinkel
+              © 2017-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
@@ -27,6 +27,7 @@
 -}
 module Lua.Primary
   ( lua_absindex
+  , lua_arith
   , lua_checkstack
   , lua_close
   , lua_concat
@@ -60,6 +61,7 @@
   , lua_pop
   , lua_pushboolean
   , lua_pushcclosure
+  , lua_pushcfunction
   , lua_pushglobaltable
   , lua_pushinteger
   , lua_pushlightuserdata
@@ -84,6 +86,7 @@
   , lua_settop
   , lua_setuservalue
   , lua_status
+  , lua_stringtonumber
   , lua_toboolean
   , lua_tocfunction
   , lua_tointegerx
@@ -116,11 +119,50 @@
 -- index (that is, one that does not depend on the stack top).
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_absindex>
-foreign import ccall unsafe "lua.h lua_absindex"
+foreign import capi unsafe "lua.h lua_absindex"
   lua_absindex :: Lua.State
                -> StackIndex     -- ^ idx
                -> IO StackIndex
 
+-- | Performs an arithmetic or bitwise operation over the two values (or
+-- one, in the case of negations) at the top of the stack, with the
+-- value at the top being the second operand, pops these values, and
+-- pushes the result of the operation. The function follows the
+-- semantics of the corresponding Lua operator (that is, it may call
+-- metamethods).
+--
+-- The value of @op@ must be one of the following constants:
+--
+-- -   __@LUA_OPADD@:__ performs addition (@+@)
+-- -   __@LUA_OPSUB@:__ performs subtraction (@-@)
+-- -   __@LUA_OPMUL@:__ performs multiplication (@*@)
+-- -   __@LUA_OPDIV@:__ performs float division (@\/@)
+-- -   __@LUA_OPIDIV@:__ performs floor division (@\/\/@)
+-- -   __@LUA_OPMOD@:__ performs modulo (@%@)
+-- -   __@LUA_OPPOW@:__ performs exponentiation (@^@)
+-- -   __@LUA_OPUNM@:__ performs mathematical negation (unary @-@)
+-- -   __@LUA_OPBNOT@:__ performs bitwise NOT (@~@)
+-- -   __@LUA_OPBAND@:__ performs bitwise AND (@&@)
+-- -   __@LUA_OPBOR@:__ performs bitwise OR (@|@)
+-- -   __@LUA_OPBXOR@:__ performs bitwise exclusive OR (@~@)
+-- -   __@LUA_OPSHL@:__ performs left shift (@\<\<@)
+-- -   __@LUA_OPSHR@:__ performs right shift (@>>@)
+--
+-- __WARNING__: @lua_arith@ is unsafe in Haskell: if the call to a
+-- metamethod triggers an error, then that error cannot be handled and
+-- will lead to an unrecoverable program crash. Consider using the
+-- @'Lua.hslua_arith'@ ersatz function instead. Likewise, the metamethod
+-- may not call a Haskell function unless the library was compiled
+-- without @allow-unsafe-gc@.
+--
+-- <https://www.lua.org/manual/5.3/manual.html#lua_arith>.
+foreign import capi SAFTY "lua.h lua_arith"
+  lua_arith :: State -> ArithOPCode {- ^ op -} -> IO ()
+{-# WARNING lua_arith
+      [ "This is an unsafe function, errors will lead to a program crash;"
+      , "consider using hslua_arith instead."
+      ] #-}
+
 -- | Ensures that the stack has space for at least @n@ extra slots (that
 -- is, that you can safely push up to @n@ values into it). It returns
 -- false if it cannot fulfill the request, either because it would cause
@@ -142,7 +184,7 @@
 -- will probably need to close states as soon as they are not needed.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_close>
-foreign import ccall safe "lua.h lua_close"
+foreign import capi safe "lua.h lua_close"
   lua_close :: Lua.State -> IO ()
 
 -- | Concatenates the @n@ values at the top of the stack, pops them, and
@@ -157,7 +199,7 @@
 -- cause an unrecoverable crash an error if any of the concatenated
 -- values causes an error when executing a metamethod. Consider using
 -- the @'Lua.hslua_concat'@ ersatz function instead.
-foreign import ccall SAFTY "lua.h lua_concat"
+foreign import capi SAFTY "lua.h lua_concat"
   lua_concat :: State -> CInt {- ^ n -} -> IO ()
 {-# WARNING lua_concat
       [ "This is an unsafe function, it will cause a program crash if"
@@ -170,7 +212,7 @@
 -- not affected.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_copy>
-foreign import ccall unsafe "lua.h lua_copy"
+foreign import capi unsafe "lua.h lua_copy"
   lua_copy :: Lua.State
            -> StackIndex  -- ^ fromidx
            -> StackIndex  -- ^ toidx
@@ -185,7 +227,7 @@
 -- can use the function @lua_newtable@.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_createtable>.
-foreign import ccall SAFTY "lua.h lua_createtable"
+foreign import capi SAFTY "lua.h lua_createtable"
   lua_createtable :: Lua.State
                   -> CInt -- ^ narr
                   -> CInt -- ^ nrec
@@ -195,7 +237,7 @@
 --
 -- See the Lua docs at
 -- <https://www.lua.org/manual/5.3/manual.html#lua_gc>.
-foreign import ccall safe "lua.h lua_gc"
+foreign import capi safe "lua.h lua_gc"
   lua_gc :: Lua.State -> GCCode {- ^ what -} -> CInt {- ^ data -} -> IO CInt
 
 -- | Pushes onto the stack the value of the global name. Returns the
@@ -209,7 +251,7 @@
 -- compiled without @allow-unsafe-gc@.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_getglobal>.
-foreign import ccall SAFTY "lua.h lua_getglobal"
+foreign import capi SAFTY "lua.h lua_getglobal"
   lua_getglobal :: State -> CString {- ^ name -} -> IO TypeCode
 {-# WARNING lua_getglobal
       [ "This is an unsafe function, errors will lead to a program crash;"
@@ -221,7 +263,7 @@
 -- function returns @0@ and pushes nothing on the stack.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_getmetatable>.
-foreign import ccall unsafe "lua.h lua_getmetatable"
+foreign import capi unsafe "lua.h lua_getmetatable"
   lua_getmetatable :: Lua.State -> StackIndex -> IO LuaBool
 
 -- | Pushes onto the stack the value @t[k]@, where @t@ is the value at
@@ -242,7 +284,7 @@
 -- compiled without @allow-unsafe-gc@.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_gettable>.
-foreign import ccall SAFTY "lua.h lua_gettable"
+foreign import capi SAFTY "lua.h lua_gettable"
   lua_gettable :: Lua.State -> StackIndex {- ^ index -} -> IO TypeCode
 {-# WARNING lua_gettable
       [ "This is an unsafe function, errors will lead to a program crash;"
@@ -254,7 +296,7 @@
 -- stack (and so 0 means an empty stack).
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_gettop>
-foreign import ccall unsafe "lua.h lua_gettop"
+foreign import capi unsafe "lua.h lua_gettop"
   lua_gettop :: Lua.State -> IO StackIndex
 
 -- | Pushes onto the stack the Lua value associated with the full
@@ -263,7 +305,7 @@
 -- Returns the type of the pushed value.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_getuservalue>
-foreign import ccall unsafe "lua.h lua_getuservalue"
+foreign import capi unsafe "lua.h lua_getuservalue"
   lua_getuservalue :: Lua.State -> StackIndex -> IO TypeCode
 
 -- | Moves the top element into the given valid index, shifting up the
@@ -286,7 +328,7 @@
 -- function, and @'Lua.FALSE'@ otherwise.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_iscfunction>
-foreign import ccall unsafe "lua.h lua_iscfunction"
+foreign import capi unsafe "lua.h lua_iscfunction"
   lua_iscfunction :: Lua.State -> StackIndex -> IO LuaBool
 
 -- | Returns @'Lua.TRUE'@ if the value at the given index is a
@@ -301,7 +343,7 @@
 -- integer), and @'Lua.FALSE'@ otherwise.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_isinteger>
-foreign import ccall unsafe "lua.h lua_isinteger"
+foreign import capi unsafe "lua.h lua_isinteger"
   lua_isinteger :: Lua.State -> StackIndex -> IO LuaBool
 
 -- | Returns @'Lua.TRUE'@ if the value at the given index is a
@@ -338,7 +380,7 @@
 -- otherwise.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_isnumber>
-foreign import ccall unsafe "lua.h lua_isnumber"
+foreign import capi unsafe "lua.h lua_isnumber"
   lua_isnumber :: Lua.State -> StackIndex -> IO LuaBool
 
 -- | Returns @'Lua.TRUE'@ if the value at the given index is a
@@ -346,7 +388,7 @@
 -- @'Lua.FALSE'@ otherwise.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_isstring>
-foreign import ccall unsafe "lua.h lua_isstring"
+foreign import capi unsafe "lua.h lua_isstring"
   lua_isstring :: Lua.State -> StackIndex -> IO LuaBool
 
 -- | Returns @'Lua.TRUE'@ if the value at the given index is a
@@ -367,7 +409,7 @@
 -- userdata (either full or light), and @'Lua.FALSE'@ otherwise.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_isuserdata>
-foreign import ccall unsafe "lua.h lua_isuserdata"
+foreign import capi unsafe "lua.h lua_isuserdata"
   lua_isuserdata :: Lua.State -> StackIndex -> IO LuaBool
 
 -- | Loads a Lua chunk (without running it). If there are no errors,
@@ -405,7 +447,7 @@
 -- always leave the stack unmodified when returning.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_load>.
-foreign import ccall safe "lua.h lua_load"
+foreign import capi safe "lua.h lua_load"
   lua_load :: Lua.State
            -> Lua.Reader     -- ^ reader
            -> Ptr ()         -- ^ data
@@ -422,7 +464,7 @@
 -- Threads are subject to garbage collection, like any Lua object.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_newthread>
-foreign import ccall SAFTY "lua.h lua_newthread"
+foreign import capi SAFTY "lua.h lua_newthread"
   lua_newthread :: Lua.State -> IO Lua.State
 
 -- | This function allocates a new block of memory with the given size,
@@ -430,7 +472,7 @@
 -- returns this address. The host program can freely use this memory.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_newuserdata>.
-foreign import ccall SAFTY "lua.h lua_newuserdata"
+foreign import capi SAFTY "lua.h lua_newuserdata"
   lua_newuserdata :: Lua.State -> CSize -> IO (Ptr ())
 
 
@@ -468,7 +510,7 @@
 -- cause an unrecoverable crash an error if the given key is neither
 -- @nil@ nor present in the table. Consider using the @'Lua.hslua_next'@
 -- ersatz function instead.
-foreign import ccall SAFTY "lua.h lua_next"
+foreign import capi SAFTY "lua.h lua_next"
   lua_next :: State -> StackIndex {- ^ index -} -> IO LuaBool
 {-# WARNING lua_next
       [ "This is an unsafe function, it will cause a program crash if"
@@ -527,7 +569,7 @@
 -- | Pushes a boolean value with the given value onto the stack.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_pushboolean>.
-foreign import ccall unsafe "lua.h lua_pushboolean"
+foreign import capi unsafe "lua.h lua_pushboolean"
   lua_pushboolean :: Lua.State -> LuaBool -> IO ()
 
 -- | Pushes a new C closure onto the stack.
@@ -546,12 +588,25 @@
 -- The maximum value for @n@ is 255.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_pushcclosure>.
-foreign import ccall SAFTY "lua.h lua_pushcclosure"
+foreign import capi SAFTY "lua.h lua_pushcclosure"
   lua_pushcclosure :: Lua.State
                    -> CFunction   -- ^ fn
                    -> NumArgs     -- ^ n
                    -> IO ()
 
+-- | Pushes a C function onto the stack. This function receives a
+-- pointer to a C function and pushes onto the stack a Lua value of type
+-- @function@ that, when called, invokes the corresponding C function.
+--
+-- Any function to be callable by Lua must follow the correct protocol
+-- to receive its parameters and return its results (see 'CFunction').
+--
+-- <https://www.lua.org/manual/5.3/manual.html#lua_pushcfunction>.
+foreign import capi unsafe "lua.h lua_pushcfunction"
+  lua_pushcfunction :: Lua.State
+                    -> CFunction   -- ^ fn
+                    -> IO ()
+
 -- | Pushes the global environment onto the stack.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_pushglobaltable>
@@ -561,7 +616,7 @@
 -- | Pushes an integer with with the given value onto the stack.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_pushinteger>.
-foreign import ccall unsafe "lua.h lua_pushinteger"
+foreign import capi unsafe "lua.h lua_pushinteger"
   lua_pushinteger :: Lua.State -> Lua.Integer -> IO ()
 
 -- | Pushes a light userdata onto the stack.
@@ -573,7 +628,7 @@
 -- to "any" light userdata with the same C address.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_pushlightuserdata>.
-foreign import ccall unsafe "lua.h lua_pushlightuserdata"
+foreign import capi unsafe "lua.h lua_pushlightuserdata"
   lua_pushlightuserdata :: Lua.State -> Ptr a -> IO ()
 
 -- | Pushes the string pointed to by @s@ with size @len@ onto the stack.
@@ -585,7 +640,7 @@
 -- Returns a pointer to the internal copy of the string.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_pushlstring>.
-foreign import ccall SAFTY "lua.h lua_pushlstring"
+foreign import capi SAFTY "lua.h lua_pushlstring"
   lua_pushlstring :: Lua.State
                   -> Ptr CChar    -- ^ s
                   -> CSize        -- ^ len
@@ -594,13 +649,13 @@
 -- | Pushes a nil value onto the stack.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_pushnil>.
-foreign import ccall unsafe "lua.h lua_pushnil"
+foreign import capi unsafe "lua.h lua_pushnil"
   lua_pushnil :: Lua.State -> IO ()
 
 -- | Pushes a float with the given value onto the stack.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_pushnumber>.
-foreign import ccall unsafe "lua.h lua_pushnumber"
+foreign import capi unsafe "lua.h lua_pushnumber"
   lua_pushnumber :: Lua.State -> Lua.Number -> IO ()
 
 
@@ -620,13 +675,13 @@
 -- thread is the main thread of its state.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_pushthread>.
-foreign import ccall unsafe "lua.h lua_pushthread"
+foreign import capi unsafe "lua.h lua_pushthread"
   lua_pushthread :: Lua.State -> IO CInt
 
 -- | Pushes a copy of the element at the given index onto the stack.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_pushvalue>
-foreign import ccall unsafe "lua.h lua_pushvalue"
+foreign import capi unsafe "lua.h lua_pushvalue"
   lua_pushvalue :: Lua.State -> StackIndex -> IO ()
 
 -- | Returns @True@ if the two values in indices @idx1@ and @idx2@ are
@@ -635,7 +690,7 @@
 -- are not valid.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_rawequal>
-foreign import ccall unsafe "lua.h lua_rawequal"
+foreign import capi unsafe "lua.h lua_rawequal"
   lua_rawequal :: Lua.State
                -> StackIndex  -- ^ idx1
                -> StackIndex  -- ^ idx2
@@ -645,16 +700,17 @@
 -- metamethods).
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_rawget>.
-foreign import ccall unsafe "lua.h lua_rawget"
-  lua_rawget :: Lua.State -> StackIndex -> IO ()
+foreign import capi unsafe "lua.h lua_rawget"
+  lua_rawget :: Lua.State -> StackIndex -> IO TypeCode
 
 -- | Pushes onto the stack the value @t[n]@, where @t@ is the table at
 -- the given index. The access is raw, that is, it does not invoke the
 -- @__index@ metamethod.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_rawgeti>.
-foreign import ccall unsafe "lua.h lua_rawgeti"
-  lua_rawgeti :: Lua.State -> StackIndex -> Lua.Integer {- ^ n -} -> IO ()
+foreign import capi unsafe "lua.h lua_rawgeti"
+  lua_rawgeti :: Lua.State -> StackIndex -> Lua.Integer {- ^ n -}
+              -> IO TypeCode
 
 -- | Returns the raw "length" of the value at the given index: for
 -- strings, this is the string length; for tables, this is the result of
@@ -663,14 +719,14 @@
 -- values, it is 0.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_rawlen>.
-foreign import ccall unsafe "lua.h lua_rawlen"
+foreign import capi unsafe "lua.h lua_rawlen"
   lua_rawlen :: Lua.State -> StackIndex -> IO CSize
 
 -- | Similar to @'lua_settable'@, but does a raw assignment (i.e.,
 -- without metamethods).
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_rawset>.
-foreign import ccall SAFTY "lua.h lua_rawset"
+foreign import capi SAFTY "lua.h lua_rawset"
   lua_rawset :: Lua.State -> StackIndex -> IO ()
 
 -- | Does the equivalent of @t[i] = v@, where @t@ is the table at the
@@ -680,7 +736,7 @@
 -- that is, it does not invoke the @__newindex@ metamethod.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_rawseti>.
-foreign import ccall SAFTY "lua.h lua_rawseti"
+foreign import capi SAFTY "lua.h lua_rawseti"
   lua_rawseti :: Lua.State -> StackIndex -> Lua.Integer -> IO ()
 
 -- | Removes the element at the given valid index, shifting down the
@@ -726,7 +782,7 @@
 -- library was compiled without @allow-unsafe-gc@.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_setglobal>.
-foreign import ccall SAFTY "lua.h lua_setglobal"
+foreign import capi SAFTY "lua.h lua_setglobal"
   lua_setglobal :: State -> CString {- ^ name -} -> IO ()
 {-# WARNING lua_setglobal
       [ "This is an unsafe function, errors will lead to a program crash;"
@@ -737,7 +793,7 @@
 -- the value at the given index.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_setmetatable>.
-foreign import ccall unsafe "lua.h lua_setmetatable"
+foreign import capi unsafe "lua.h lua_setmetatable"
   lua_setmetatable :: Lua.State -> StackIndex -> IO ()
 
 -- | Does the equivalent to @t[k] = v@, where @t@ is the value at the
@@ -756,7 +812,7 @@
 -- compiled without @allow-unsafe-gc@.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_settable>
-foreign import ccall SAFTY "lua.h lua_settable"
+foreign import capi SAFTY "lua.h lua_settable"
   lua_settable :: Lua.State -> StackIndex {- ^ index -} -> IO ()
 {-# WARNING lua_settable
       [ "This is an unsafe function, errors will lead to a program crash;"
@@ -769,14 +825,14 @@
 -- removed.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_settop>
-foreign import ccall unsafe "lua.h lua_settop"
+foreign import capi unsafe "lua.h lua_settop"
   lua_settop :: Lua.State -> StackIndex {- ^ index -} -> IO ()
 
 -- | Pops a value from the stack and sets it as the new value associated
 -- to the full userdata at the given index.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_setuservalue>
-foreign import ccall unsafe "lua.h lua_setuservalue"
+foreign import capi unsafe "lua.h lua_setuservalue"
   lua_setuservalue :: Lua.State -> StackIndex -> IO ()
 
 -- |  Returns the status of this Lua thread.
@@ -792,9 +848,22 @@
 -- @'Lua.LUA_YIELD'@ (to resume a coroutine).
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_status>.
-foreign import ccall unsafe "lua.h lua_status"
+foreign import capi unsafe "lua.h lua_status"
   lua_status :: Lua.State -> IO StatusCode
 
+-- | Converts the zero-terminated string @s@ to a number, pushes that
+-- number into the stack, and returns the total size of the string, that
+-- is, its length plus one. The conversion can result in an integer or a
+-- float, according to the lexical conventions of Lua (see
+-- <https://www.lua.org/manual/5.3/manual.html#3.1 §3.1>). The string
+-- may have leading and trailing spaces and a sign. If the string is not
+-- a valid numeral, returns 0 and pushes nothing. (Note that the result
+-- can be used as a boolean, true if the conversion succeeds.)
+--
+-- <https://www.lua.org/manual/5.3/manual.html#lua_stringtonumber>.
+foreign import capi unsafe "lua.h lua_stringtonumber"
+  lua_stringtonumber :: Lua.State -> CString -> IO CSize
+
 -- | Converts the Lua value at the given index to a haskell boolean
 -- value. Like all tests in Lua, @toboolean@ returns @True@ for any Lua
 -- value different from *false* and *nil*; otherwise it returns @False@.
@@ -809,7 +878,7 @@
 -- must be a C function; otherwise, returns @Nothing@.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_tocfunction>
-foreign import ccall unsafe "lua.h lua_tocfunction"
+foreign import capi unsafe "lua.h lua_tocfunction"
   lua_tocfunction :: Lua.State -> StackIndex -> IO CFunction
 
 -- | Converts the Lua value at the given acceptable index to the signed
@@ -825,7 +894,7 @@
 -- that indicates whether the operation succeeded.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_tointegerx>
-foreign import ccall unsafe "lua.h lua_tointegerx"
+foreign import capi unsafe "lua.h lua_tointegerx"
   lua_tointegerx :: Lua.State
                  -> StackIndex       -- ^ index
                  -> Ptr LuaBool      -- ^ isnum
@@ -887,7 +956,7 @@
 -- function returns @'nullPtr'@.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_tothread>
-foreign import ccall unsafe "lua.h lua_tothread"
+foreign import capi unsafe "lua.h lua_tothread"
   lua_tothread :: Lua.State -> StackIndex -> IO Lua.State
 
 -- | If the value at the given index is a full userdata, returns its
@@ -895,14 +964,14 @@
 -- Otherwise, returns @'nullPtr'@.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_touserdata>
-foreign import ccall unsafe "lua.h lua_touserdata"
+foreign import capi unsafe "lua.h lua_touserdata"
   lua_touserdata :: Lua.State -> StackIndex -> IO (Ptr a)
 
 -- | Returns the type of the value in the given valid index, or
 -- @'Lua.LUA_TNONE'@ for a non-valid (but acceptable) index.
 --
 -- <https://www.lua.org/manual/5.3/manual.html#lua_type>
-foreign import ccall unsafe "lua.h lua_type"
+foreign import capi unsafe "lua.h lua_type"
   lua_type :: Lua.State -> StackIndex -> IO TypeCode
 
 -- | Returns the name of the type encoded by the value @tp@, which must
diff --git a/src/Lua/Types.hsc b/src/Lua/Types.hsc
--- a/src/Lua/Types.hsc
+++ b/src/Lua/Types.hsc
@@ -5,7 +5,7 @@
 Module      : Lua.Types
 Copyright   : © 2007–2012 Gracjan Polak;
               © 2012–2016 Ömer Sinan Ağacan;
-              © 2017-2021 Albert Krewinkel
+              © 2017-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
@@ -26,6 +26,7 @@
   , NumArgs (..)
   , NumResults (..)
   , OPCode (..)
+  , ArithOPCode (..)
   , StatusCode (..)
     -- * Garbage-Collection
   , GCCode (..)
@@ -36,6 +37,7 @@
 
 import Prelude hiding (Integer)
 
+import Data.Bifunctor (first)
 import Data.Int (#{type LUA_INTEGER})
 import Foreign.C (CChar, CInt, CSize)
 import Foreign.Ptr (FunPtr, Ptr)
@@ -91,11 +93,17 @@
 -- |  The type of integers in Lua.
 --
 -- By default this type is @'Int64'@, but that can be changed to
--- different values in lua. (See @LUA_INT_TYPE@ in @luaconf.h@.)
+-- different values in Lua. (See @LUA_INT_TYPE@ in @luaconf.h@.)
 --
 -- See <https://www.lua.org/manual/5.3/manual.html#lua_Integer lua_Integer>.
 newtype Integer = Integer #{type LUA_INTEGER}
-  deriving (Bounded, Enum, Eq, Integral, Num, Ord, Real, Show)
+  deriving (Bounded, Enum, Eq, Integral, Num, Ord, Real)
+-- we should be able to use deriving strategies if we decide to drop
+-- support for GHC 8.0
+instance Show Integer where
+  show (Integer i) = show i
+instance Read Integer where
+  readsPrec i = map (first Integer) . readsPrec i
 
 -- |  The type of floats in Lua.
 --
@@ -105,7 +113,13 @@
 --
 -- See <https://www.lua.org/manual/5.3/manual.html#lua_Number lua_Number>.
 newtype Number = Number #{type LUA_NUMBER}
-  deriving (Eq, Floating, Fractional, Num, Ord, Real, RealFloat, RealFrac, Show)
+  deriving (Eq, Floating, Fractional, Num, Ord, Real, RealFloat, RealFrac)
+-- we should be able to use deriving strategies if we decide to drop
+-- support for GHC 8.0
+instance Show Number where
+  show (Number n) = show n
+instance Read Number where
+  readsPrec i = map (first Number) . readsPrec i
 
 -- | Boolean value returned by a Lua C API function. This is a @'CInt'@
 -- and should be interpreted as @'False'@ iff the value is @0@, @'True'@
@@ -118,13 +132,16 @@
   deriving (Eq, Ord, Show)
 
 -- | Relational operator code.
-newtype OPCode = OPCode CInt deriving (Eq, Storable)
+newtype OPCode = OPCode CInt deriving (Eq, Storable, Show)
 
+-- | Arithmetic operator code.
+newtype ArithOPCode = ArithOPCode CInt deriving (Eq, Storable, Show)
+
 -- | Integer code used to signal the status of a thread or computation.
-newtype StatusCode = StatusCode CInt deriving (Eq, Storable)
+newtype StatusCode = StatusCode CInt deriving (Eq, Storable, Show)
 
 -- | Garbage-collection options.
-newtype GCCode = GCCode CInt deriving (Eq, Storable)
+newtype GCCode = GCCode CInt deriving (Eq, Storable, Show)
 
 -- | A stack index
 newtype StackIndex = StackIndex { fromStackIndex :: CInt }
diff --git a/src/Lua/Userdata.hs b/src/Lua/Userdata.hs
--- a/src/Lua/Userdata.hs
+++ b/src/Lua/Userdata.hs
@@ -1,7 +1,7 @@
 {-# LANGUAGE CPP #-}
 {-|
 Module      : Lua.Userdata
-Copyright   : © 2017-2021 Albert Krewinkel
+Copyright   : © 2017-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
diff --git a/test/Lua/ErsatzTests.hs b/test/Lua/ErsatzTests.hs
--- a/test/Lua/ErsatzTests.hs
+++ b/test/Lua/ErsatzTests.hs
@@ -1,6 +1,6 @@
 {-|
 Module      : Lua.ErsatzTests
-Copyright   : © 2021 Albert Krewinkel
+Copyright   : © 2021-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
@@ -10,8 +10,10 @@
 module Lua.ErsatzTests (tests) where
 
 import Control.Monad (void)
-import Foreign.C.String (withCStringLen)
+import Foreign.C.String (withCString, withCStringLen)
+import Foreign.Marshal (alloca)
 import Foreign.Ptr (nullPtr)
+import Foreign.Storable (peek)
 import Lua
 import Test.Tasty (TestTree, testGroup)
 import Test.Tasty.HUnit (Assertion, HasCallStack, testCase, (@=?) )
@@ -19,7 +21,75 @@
 -- | Tests for unsafe methods.
 tests :: TestTree
 tests = testGroup "ersatz"
-  [ testGroup "global"
+  [ testGroup "arith"
+    [ "adds two numbers" =: do
+        7 `shouldBeResultOf` \l -> do
+          lua_pushinteger l 5
+          lua_pushinteger l 2
+          hslua_arith l LUA_OPADD nullPtr
+          lua_tointegerx l top nullPtr
+    , "negates number" =: do
+        (-5) `shouldBeResultOf` \l -> do
+          lua_pushinteger l 5
+          hslua_arith l LUA_OPUNM nullPtr
+          lua_tointegerx l top nullPtr
+    , "pops its arguments from the stack" =: do
+        1 `shouldBeResultOf` \l -> do
+          old <- lua_gettop l
+          lua_pushinteger l 4
+          lua_pushinteger l 3
+          hslua_arith l LUA_OPADD nullPtr
+          new <- lua_gettop l
+          return (new - old)
+    , "pops a single argument for unary negation" =: do
+        1 `shouldBeResultOf` \l -> do
+          old <- lua_gettop l
+          lua_pushinteger l 3
+          hslua_arith l LUA_OPUNM nullPtr
+          new <- lua_gettop l
+          return (new - old)
+    , "sets status to LUA_OK on success" =: do
+        (LUA_OK, 1024) `shouldBeResultOf` \l -> do
+          lua_pushinteger l 2
+          lua_pushinteger l 10
+          stts <- alloca $ \status -> do
+            hslua_arith l LUA_OPPOW status
+            peek status
+          result <- lua_tointegerx l top nullPtr
+          return (stts, result)
+    , "sets error status on error" =: do
+        LUA_ERRRUN `shouldBeResultOf` \l -> do
+          lua_pushinteger l 2
+          lua_pushboolean l TRUE
+          alloca $ \status -> do
+            hslua_arith l LUA_OPSHR status
+            peek status
+    , "runs metamethod" =: do
+        (LUA_OK, 7) `shouldBeResultOf` \l -> do
+          lua_pushinteger l 2
+          lua_createtable l 0 0
+          LUA_OK <- withCStringLen "return {__mod = function() return 7 end}" $
+            \(s, len) -> luaL_loadbuffer l s (fromIntegral len) s
+          LUA_OK <- lua_pcall l 0 1 0
+          lua_setmetatable l (-2)
+          stts <- alloca $ \status -> do
+            hslua_arith l LUA_OPMOD status
+            peek status
+          result <- lua_tointegerx l top nullPtr
+          return (stts, result)
+    , "catches runtime error in metamethod" =: do
+        LUA_ERRRUN `shouldBeResultOf` \l -> do
+          lua_pushinteger l 2
+          lua_createtable l 0 0
+          LUA_OK <- withCStringLen "return {__bor = function() error'no' end}" $
+            \(s, len) -> luaL_loadbuffer l s (fromIntegral len) s
+          LUA_OK <- lua_pcall l 0 1 0
+          lua_setmetatable l (-2)
+          alloca $ \status -> do
+            hslua_arith l LUA_OPBOR status
+            peek status
+    ]
+  , testGroup "global"
     [ "set and get global field" =: do
         (42, LUA_TNUMBER) `shouldBeResultOf` \l -> do
           lua_pushinteger l 42
@@ -38,6 +108,45 @@
           ty <- hslua_gettable l (nth 2) nullPtr
           i  <- lua_tointegerx l top nullPtr
           return (i, ty)
+    ]
+
+  , testGroup "Auxiliary"
+    [ testGroup "hslua_requiref"
+      [ "can load a module" =: do
+        LUA_TTABLE `shouldBeResultOf` \l -> do
+          lua_pushcfunction l luaopen_package
+          LUA_OK <- lua_pcall l 0 0 0
+          withCString "math" $ \s ->
+            hsluaL_requiref l s luaopen_math FALSE nullPtr
+          lua_type l (-1)
+
+      , "returns LUA_OK on success" =: do
+        LUA_OK `shouldBeResultOf` \l -> do
+          lua_pushcfunction l luaopen_package
+          LUA_OK <- lua_pcall l 0 0 0
+          alloca $ \status -> do
+            withCString "math" $ \s ->
+              hsluaL_requiref l s luaopen_package FALSE status
+            peek status
+
+      , "sets global if flag is set" =: do
+        LUA_TTABLE `shouldBeResultOf` \l -> do
+          luaL_openlibs l
+          withCString "mathematics" $ \s ->
+            hsluaL_requiref l s luaopen_math TRUE nullPtr
+          withCStringLen "mathematics" $ \(name, len) ->
+            hslua_getglobal l name (fromIntegral len) nullPtr
+
+      , "catches errors" =: do
+        LUA_ERRRUN `shouldBeResultOf` \l -> do
+          -- unset registry
+          lua_pushnil l
+          lua_copy l (-1) LUA_REGISTRYINDEX
+          alloca $ \status -> do
+            withCString "math" $ \s ->
+              hsluaL_requiref l s luaopen_package FALSE status
+            peek status
+      ]
     ]
   ]
 
diff --git a/test/Lua/PrimaryTests.hs b/test/Lua/PrimaryTests.hs
new file mode 100644
--- /dev/null
+++ b/test/Lua/PrimaryTests.hs
@@ -0,0 +1,50 @@
+{-# OPTIONS_GHC -Wno-unused-do-bind        #-}
+{-|
+Module      : Lua.PrimaryTests
+Copyright   : © 2021-2022 Albert Krewinkel
+License     : MIT
+Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
+
+Tests for bindings to primary API functions.
+-}
+module Lua.PrimaryTests (tests) where
+
+import Foreign.C.String (withCString)
+import Foreign.Ptr (nullPtr)
+import Lua
+import Test.Tasty (TestTree, testGroup)
+import Test.Tasty.HUnit (Assertion, HasCallStack, testCase, (@=?) )
+
+-- | Tests for unsafe methods.
+tests :: TestTree
+tests = testGroup "Primary"
+  [ testGroup "C functions"
+    [ "can push and call luaopen_math" =: do
+        LUA_TTABLE `shouldBeResultOf` \l -> do
+          lua_pushcfunction l luaopen_math
+          lua_pcall l 0 1 0
+          lua_type l (-1)
+    ]
+  , testGroup "lua_stringtonumber"
+    [ "converts a string to a number" =: do
+        55 `shouldBeResultOf` \l -> do
+          _ <- withCString "55" $ lua_stringtonumber l
+          lua_tointegerx l top nullPtr
+    , "returns length (incl NULL) of the string on success" =: do
+        4 `shouldBeResultOf` \l -> do
+          withCString "512" $ lua_stringtonumber l
+    , "returns zero on failure" =: do
+        0 `shouldBeResultOf` \l -> do
+          withCString "NaN" $ lua_stringtonumber l
+    ]
+  ]
+
+infix  3 =:
+(=:) :: String -> Assertion -> TestTree
+(=:) = testCase
+
+shouldBeResultOf :: (HasCallStack, Eq a, Show a)
+                 => a -> (State -> IO a) -> Assertion
+shouldBeResultOf expected luaOp = do
+  result <- withNewState luaOp
+  expected @=? result
diff --git a/test/Lua/UnsafeTests.hs b/test/Lua/UnsafeTests.hs
--- a/test/Lua/UnsafeTests.hs
+++ b/test/Lua/UnsafeTests.hs
@@ -1,7 +1,8 @@
 {-# OPTIONS_GHC -Wno-warnings-deprecations #-}
+{-# OPTIONS_GHC -Wno-unused-do-bind        #-}
 {-|
 Module      : Lua.UnsafeTests
-Copyright   : © 2021 Albert Krewinkel
+Copyright   : © 2021-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
@@ -117,6 +118,36 @@
         lua_createtable l 0 0
         lua_pushnil l
         lua_next l (nth 2)
+    ]
+
+  , testGroup "arith"
+    [ "multiplies two numbers" =: do
+        10 `shouldBeResultOf` \l -> do
+          lua_pushinteger l 5
+          lua_pushinteger l 2
+          lua_arith l LUA_OPMUL
+          lua_tointegerx l top nullPtr
+    , "divides number" =: do
+        2 `shouldBeResultOf` \l -> do
+          lua_pushinteger l 6
+          lua_pushinteger l 3
+          lua_arith l LUA_OPIDIV
+          lua_tointegerx l top nullPtr
+    , "pops its arguments from the stack" =: do
+        1 `shouldBeResultOf` \l -> do
+          old <- lua_gettop l
+          lua_pushinteger l 4
+          lua_pushinteger l 3
+          lua_arith l LUA_OPSUB
+          new <- lua_gettop l
+          return (new - old)
+    , "pops a single argument for unary negation" =: do
+        1 `shouldBeResultOf` \l -> do
+          old <- lua_gettop l
+          lua_pushinteger l 9
+          lua_arith l LUA_OPUNM
+          new <- lua_gettop l
+          return (new - old)
     ]
   ]
 
diff --git a/test/test-lua.hs b/test/test-lua.hs
--- a/test/test-lua.hs
+++ b/test/test-lua.hs
@@ -1,7 +1,8 @@
+{-# OPTIONS_GHC -Wno-unused-do-bind #-}
 {-# LANGUAGE CPP #-}
 {-|
 Module      : Main
-Copyright   : © 2021 Albert Krewinkel
+Copyright   : © 2021-2022 Albert Krewinkel
 License     : MIT
 Maintainer  : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
 Stability   : beta
@@ -25,6 +26,7 @@
 import Test.Tasty (TestTree, defaultMain, testGroup)
 import Test.Tasty.HUnit (Assertion, HasCallStack, assertBool, testCase, (@=?))
 import qualified Lua.ErsatzTests
+import qualified Lua.PrimaryTests
 import qualified Lua.UnsafeTests
 
 -- | Runs tests.
@@ -113,6 +115,11 @@
         LUA_TNUMBER `shouldBeResultOf` \l -> do
           lua_pushinteger l 0
           lua_type l top
+
+    , "show Integer" =: ("23" @=? show (23 :: Lua.Integer))
+    , "read Integer" =: ((23 :: Lua.Integer) @=? read "23")
+    , "show Number" =: ("23.5" @=? show (23.5 :: Lua.Number))
+    , "read Number" =: ((23.5 :: Lua.Number) @=? read "23.5")
     ]
 
   , testGroup "nil"
@@ -390,6 +397,7 @@
 #endif
     ]
 
+  , Lua.PrimaryTests.tests
   , Lua.UnsafeTests.tests
   , Lua.ErsatzTests.tests
   ]
