{-|
Module      : Foreign.Lua.Module
Copyright   : © 2019 Albert Krewinkel
License     : MIT
Maintainer  : Albert Krewinkel <albert+hslua@zeitkraut.de>
Stability   : alpha
Portability : Requires GHC 8 or later.

Utility functions for HsLua modules.
-}
module Foreign.Lua.Module
  ( requirehs
  , preloadhs
  , addfield
  , addfunction
  , create
  )
where

import Control.Monad (unless)
import Foreign.Lua.Core
import Foreign.Lua.Types (Pushable, push)
import Foreign.Lua.FunctionCalling (ToHaskellFunction, pushHaskellFunction)

-- | Load a module, defined by a Haskell action, under the given name.
--
-- Similar to @luaL_required@: After checking "loaded" table, calls
-- @pushMod@ to push a module to the stack, and registers the result in
-- @package.loaded@ table.
--
-- The @pushMod@ function must push exactly one element to the top of
-- the stack. This is not checked, but failure to do so will lead to
-- problems. Lua's @package@ module must have been loaded by the time
-- this function is invoked.
--
-- Leaves a copy of the module on the stack.
requirehs :: String -> Lua () -> Lua ()
requirehs :: String -> Lua () -> Lua ()
requirehs String
modname Lua ()
pushMod = do
  -- get table of loaded modules
  StackIndex -> String -> Lua ()
getfield StackIndex
registryindex String
loadedTableRegistryField

  -- Check whether module has already been loaded.
  StackIndex -> String -> Lua ()
getfield StackIndex
stackTop String
modname  -- LOADED[modname]
  Bool
alreadyLoaded <- StackIndex -> Lua Bool
toboolean StackIndex
stackTop

  Bool -> Lua () -> Lua ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
alreadyLoaded (Lua () -> Lua ()) -> Lua () -> Lua ()
forall a b. (a -> b) -> a -> b
$ do
    StackIndex -> Lua ()
pop StackIndex
1  -- remove field
    Lua ()
pushMod  -- push module
    StackIndex -> Lua ()
pushvalue StackIndex
stackTop  -- make copy of module
    -- add module under the given name (LOADED[modname] = module)
    StackIndex -> String -> Lua ()
setfield (CInt -> StackIndex
nthFromTop CInt
3) String
modname

  StackIndex -> Lua ()
remove (CInt -> StackIndex
nthFromTop CInt
2)  -- remove table of loaded modules

-- | Registers a preloading function. Takes an module name and the Lua
-- operation which produces the package.
preloadhs :: String -> Lua NumResults -> Lua ()
preloadhs :: String -> Lua NumResults -> Lua ()
preloadhs String
name Lua NumResults
pushMod = do
  StackIndex -> String -> Lua ()
getfield StackIndex
registryindex String
preloadTableRegistryField
  Lua NumResults -> Lua ()
forall a. ToHaskellFunction a => a -> Lua ()
pushHaskellFunction Lua NumResults
pushMod
  StackIndex -> String -> Lua ()
setfield (CInt -> StackIndex
nthFromTop CInt
2) String
name
  StackIndex -> Lua ()
pop StackIndex
1

-- | Add a string-indexed field to the table at the top of the stack.
addfield :: Pushable a => String -> a -> Lua ()
addfield :: String -> a -> Lua ()
addfield String
name a
value = do
  String -> Lua ()
forall a. Pushable a => a -> Lua ()
push String
name
  a -> Lua ()
forall a. Pushable a => a -> Lua ()
push a
value
  StackIndex -> Lua ()
rawset (CInt -> StackIndex
nthFromTop CInt
3)

-- | Attach a function to the table at the top of the stack, using the
-- given name.
addfunction :: ToHaskellFunction a => String -> a -> Lua ()
addfunction :: String -> a -> Lua ()
addfunction String
name a
fn = do
  String -> Lua ()
forall a. Pushable a => a -> Lua ()
push String
name
  a -> Lua ()
forall a. ToHaskellFunction a => a -> Lua ()
pushHaskellFunction a
fn
  StackIndex -> Lua ()
rawset (CInt -> StackIndex
nthFromTop CInt
3)

-- | Create a new module (i.e., a Lua table).
create :: Lua ()
create :: Lua ()
create = Lua ()
newtable