packages feed

oughta-0.1.0.0: src/Oughta/Exception.hs

{-# LANGUAGE LambdaCase #-}

module Oughta.Exception
  ( Exception(..)
  , NoMatch
  , noMatch
  , throwNoMatch
  ) where

import Control.Exception qualified as X
import Control.Monad.Catch qualified as Catch
import Control.Monad.IO.Class (liftIO)
import Data.Text qualified as Text
import Data.Text.Encoding qualified as Text
import Oughta.Result qualified as OR
import Foreign.StablePtr (StablePtr)
import Foreign.StablePtr qualified as Foreign
import HsLua qualified as Lua
import HsLua.Core.Utf8 qualified as Lua.Utf8

-- | Exceptions that may be thrown from Lua code.
--
-- Must be storable on the Lua stack. Uses opaque 'Ptr's for data that cannot be
-- inspected by Lua.
data Exception
  = LuaException Lua.Exception
    -- | @fail@ was called.
  | Failure NoMatch

-- | Wrapper for 'OR.Failure'
newtype NoMatch = NoMatch (StablePtr OR.Failure)

instance Show NoMatch where
  -- can't do IO here, but this Show instance won't be used anyway
  show (NoMatch {}) = "oughta: no match"

instance Show Exception where
  show =
    \case
      LuaException e -> show e
      Failure f -> show f

instance X.Exception Exception

noMatch :: NoMatch -> IO OR.Failure
noMatch (NoMatch sp) = Foreign.deRefStablePtr sp

throwNoMatch :: OR.Failure -> Lua.LuaE Exception a
throwNoMatch failure = do
  sp <- liftIO (Foreign.newStablePtr failure)
  Catch.throwM (Failure (NoMatch sp))

instance Lua.LuaError Exception where
  popException = do
    top <- Lua.tostring Lua.top
    case top of
      Just str -> do
        Lua.pop 1
        pure (LuaException (Lua.Exception (Text.unpack (Text.decodeUtf8Lenient str))))
      Nothing -> do
        top' <- Lua.touserdata Lua.top
        case top' of
          Just ptr -> do
            Lua.pop 1
            pure (Failure (NoMatch (Foreign.castPtrToStablePtr ptr)))
          Nothing -> Lua.failLua "Bad exception!"

  pushException =
    \case
      LuaException (Lua.Exception msg) -> Lua.pushstring (Lua.Utf8.fromString msg)
      Failure (NoMatch sp) -> Lua.pushlightuserdata (Foreign.castStablePtrToPtr sp)

  luaException s = LuaException (Lua.luaException s)