Main.hs 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. {-# LANGUAGE NamedFieldPuns #-}
  2. module Main where
  3. import qualified Control.Applicative as App
  4. import qualified Control.Exception as Exn
  5. import qualified Control.Monad as Monad
  6. import qualified Foreign.Lua as Lua
  7. data Config = Config
  8. { cfgHost :: String,
  9. cfgPort :: Int,
  10. cfgFiles :: [String]
  11. }
  12. deriving (Show)
  13. -- a very barebones attempt at doing error conversion: we ignore a
  14. -- bunch of stuff, but we do translate Lua errors into Haskell errors
  15. errorConversion :: Lua.ErrorConversion
  16. errorConversion = Lua.ErrorConversion {Lua.errorToException, Lua.addContextToException, Lua.alternative, Lua.exceptionToError}
  17. where
  18. errorToException = Lua.throwTopMessageWithState
  19. addContextToException _ x = x
  20. alternative x y = x App.<|> y
  21. exceptionToError = id
  22. readConfig :: FilePath -> IO Config
  23. readConfig configPath =
  24. -- set up a new Lua state, and make sure we close it at the end
  25. Exn.bracket Lua.newstate Lua.close $ \st ->
  26. -- we should set up a mechanism to convert Lua/Haskell errors into
  27. -- one another, but that seems like a lot of work here, so this
  28. -- just panics whenever any errors happen, hence `unsafeRunWith`
  29. Lua.runWithConverter errorConversion st $ do
  30. -- this loads the stdlib into Lua, but you probably don't
  31. -- actually want this in a config file! (it allows e.g. file IO
  32. -- and stuff)
  33. Lua.openlibs
  34. -- load the actual config path
  35. res <- Lua.loadfile configPath
  36. -- if that didn't work, then take the Lua error and turn it into
  37. -- a Haskell exception
  38. Monad.when
  39. (res /= Lua.OK)
  40. Lua.throwErrorAsException
  41. -- that simply loads the code as a zero-argument function: now
  42. -- call it
  43. Lua.call 0 0
  44. -- use the `readGlobal` helper to extract the globals defined by
  45. -- the config file...
  46. cfgHost <- readGlobal "host"
  47. cfgPort <- readGlobal "port"
  48. cfgFiles <- readGlobal "files"
  49. -- and return the values
  50. return Config {cfgHost, cfgPort, cfgFiles}
  51. -- | Read the global variable @name@ into a Haskell value
  52. readGlobal :: Lua.Peekable t => String -> Lua.Lua t
  53. readGlobal name = do
  54. -- this pushes the global named `name` onto the top of the stack
  55. Lua.getglobal name
  56. -- this finds out what index is at the top of the stack
  57. idx <- Lua.gettop
  58. -- this uses the `peek` functionality, already parameterized by a
  59. -- typeclass, to convert the value at the given index into the
  60. -- appropriate Haskell type
  61. value <- Lua.peek idx
  62. -- pop the global, now that we've already got it in Haskell-land
  63. Lua.pop 1
  64. -- ...and return it
  65. return value
  66. main :: IO ()
  67. main = do
  68. cfg <- readConfig "sample.lua"
  69. print cfg