-
-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathPeekable.hs
More file actions
256 lines (216 loc) · 8.63 KB
/
Peekable.hs
File metadata and controls
256 lines (216 loc) · 8.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-|
Module : HsLua.Class.Peekable
Copyright : © 2007–2012 Gracjan Polak;
© 2012–2016 Ömer Sinan Ağacan;
© 2017-2021 Albert Krewinkel
License : MIT
Maintainer : Albert Krewinkel <tarleb+hslua@zeitkraut.de>
Stability : beta
Portability : non-portable (depends on GHC)
Sending haskell objects to the lua stack.
-}
module HsLua.Class.Peekable
( Peekable (..)
, PeekError (..)
, peekKeyValuePairs
, peekList
, reportValueOnFailure
, inContext
) where
import Control.Monad ((>=>))
import Data.ByteString (ByteString)
import Data.Map (Map, fromList)
import Data.Set (Set)
import HsLua.Core as Lua
import HsLua.Marshalling.Peek (runPeeker)
import Foreign.Ptr (Ptr)
import qualified Control.Monad.Catch as Catch
import qualified Data.Set as Set
import qualified Data.Text as T
import qualified Data.ByteString.Lazy as BL
import qualified HsLua.Core.Unsafe as Unsafe
import qualified HsLua.Marshalling as Peek
-- | Use @test@ to check whether the value at stack index @n@ has the
-- correct type and use @peekfn@ to convert it to a haskell value if
-- possible. Throws and exception if the test failes with the expected
-- type name as part of the message.
typeChecked :: forall e a. LuaError e
=> ByteString -- ^ expected type
-> (StackIndex -> LuaE e Bool) -- ^ pre-condition Checker
-> (StackIndex -> LuaE e a) -- ^ retrieval function
-> StackIndex -> LuaE e a
typeChecked expectedType test peekfn idx = do
v <- test idx
if v
then peekfn idx
else throwTypeMismatchError expectedType idx
-- | Report the expected and actual type of the value under the given
-- index if conversion failed.
reportValueOnFailure :: forall e a. PeekError e
=> ByteString
-> (StackIndex -> LuaE e (Maybe a))
-> StackIndex -> LuaE e a
reportValueOnFailure expected peekMb idx = do
res <- peekMb idx
case res of
(Just x) -> return x
Nothing -> throwTypeMismatchError expected idx
-- | A value that can be read from the Lua stack.
class Peekable a where
-- | Check if at index @n@ there is a convertible Lua value and if so return
-- it. Throws a @'Lua.Exception'@ otherwise.
peek :: PeekError e => StackIndex -> LuaE e a
instance Peekable () where
peek = reportValueOnFailure "nil" $ \idx -> do
isNil <- isnil idx
return (if isNil then Just () else Nothing)
instance Peekable Lua.Integer where
peek = reportValueOnFailure "integer" tointeger
instance Peekable Lua.Number where
peek = reportValueOnFailure "number" tonumber
instance Peekable ByteString where
peek = runPeeker Peek.peekByteString >=> Peek.force
instance Peekable Bool where
peek = toboolean
instance Peekable CFunction where
peek = reportValueOnFailure "C function" tocfunction
instance Peekable (Ptr a) where
peek = reportValueOnFailure "userdata" touserdata
instance Peekable Lua.State where
peek = reportValueOnFailure "Lua state (i.e., a thread)" tothread
instance Peekable T.Text where
peek = runPeeker Peek.peekText >=> Peek.force
instance Peekable BL.ByteString where
peek = runPeeker Peek.peekLazyByteString >=> Peek.force
instance Peekable Prelude.Integer where
peek = runPeeker Peek.peekIntegral >=> Peek.force
instance Peekable Int where
peek = runPeeker Peek.peekIntegral >=> Peek.force
instance Peekable Float where
peek = runPeeker Peek.peekRealFloat >=> Peek.force
instance Peekable Double where
peek = runPeeker Peek.peekRealFloat >=> Peek.force
instance {-# OVERLAPS #-} Peekable [Char] where
peek = runPeeker Peek.peekString >=> Peek.force
instance Peekable a => Peekable [a] where
peek = peekList
instance (Ord a, Peekable a, Peekable b) => Peekable (Map a b) where
peek = fmap fromList . peekKeyValuePairs
instance (Ord a, Peekable a) => Peekable (Set a) where
peek = -- All keys with non-nil values are in the set
fmap (Set.fromList . map fst . filter snd) . peekKeyValuePairs
-- | Read a table into a list
peekList :: (PeekError e, Peekable a) => StackIndex -> LuaE e [a]
peekList = typeChecked "table" istable $ \idx -> do
let elementsAt [] = return []
elementsAt (i : is) = do
x <- (rawgeti idx i *> peek top) `Catch.finally` pop 1
(x:) <$> elementsAt is
listLength <- fromIntegral <$> rawlen idx
inContext "Could not read list:" (elementsAt [1..listLength])
-- | Read a table into a list of pairs.
peekKeyValuePairs :: (Peekable a, Peekable b, PeekError e)
=> StackIndex -> LuaE e [(a, b)]
peekKeyValuePairs = typeChecked "table" istable $ \idx -> do
let remainingPairs = do
res <- nextPair (if idx < 0 then idx - 1 else idx)
case res of
Nothing -> [] <$ return ()
Just a -> (a:) <$> remainingPairs
pushnil
remainingPairs
-- ensure the remaining key is removed from the stack on exception
`Catch.onException` pop 1
-- | Get the next key-value pair from a table. Assumes the last key to
-- be on the top of the stack and the table at the given index @idx@.
nextPair :: (PeekError e, Peekable a, Peekable b)
=> StackIndex -> LuaE e (Maybe (a, b))
nextPair idx = do
hasNext <- Unsafe.next idx
if hasNext
then let pair = (,) <$> inContext "Could not read key of key-value pair:"
(peek (nth 2))
<*> inContext "Could not read value of key-value pair:"
(peek (nth 1))
in Just <$> pair `Catch.finally` pop 1
-- removes the value, keeps the key
else return Nothing
-- | Specify a name for the context in which a computation is run. The
-- name is added to the error message in case of an exception.
inContext :: forall e a. PeekError e
=> String -> LuaE e a -> LuaE e a
inContext ctx op = try op >>= \case
Right x -> return x
Left (err :: e) -> Catch.throwM $
luaException @e (ctx ++ "\n\t" ++ messageFromException err)
-- | Exceptions that are to be used with 'peek' and similar functions
-- must be instances of this class. It ensures that error can be amended
-- with the context in which they happened.
class LuaError e => PeekError e where
messageFromException :: e -> String
instance PeekError Lua.Exception where
messageFromException = Lua.exceptionMessage
--
-- Tuples
--
instance {-# OVERLAPPABLE #-}
(Peekable a, Peekable b) =>
Peekable (a, b)
where
peek = typeChecked "table" istable $ \idx ->
(,) <$> nthValue idx 1 <*> nthValue idx 2
instance {-# OVERLAPPABLE #-}
(Peekable a, Peekable b, Peekable c) =>
Peekable (a, b, c)
where
peek = typeChecked "table" istable $ \idx ->
(,,) <$> nthValue idx 1 <*> nthValue idx 2 <*> nthValue idx 3
instance {-# OVERLAPPABLE #-}
(Peekable a, Peekable b, Peekable c, Peekable d) =>
Peekable (a, b, c, d)
where
peek = typeChecked "table" istable $ \idx ->
(,,,) <$> nthValue idx 1 <*> nthValue idx 2 <*> nthValue idx 3
<*> nthValue idx 4
instance {-# OVERLAPPABLE #-}
(Peekable a, Peekable b, Peekable c, Peekable d, Peekable e) =>
Peekable (a, b, c, d, e)
where
peek = typeChecked "table" istable $ \idx ->
(,,,,) <$> nthValue idx 1 <*> nthValue idx 2 <*> nthValue idx 3
<*> nthValue idx 4 <*> nthValue idx 5
instance {-# OVERLAPPABLE #-}
(Peekable a, Peekable b, Peekable c, Peekable d, Peekable e, Peekable f) =>
Peekable (a, b, c, d, e, f)
where
peek = typeChecked "table" istable $ \idx ->
(,,,,,) <$> nthValue idx 1 <*> nthValue idx 2 <*> nthValue idx 3
<*> nthValue idx 4 <*> nthValue idx 5 <*> nthValue idx 6
instance {-# OVERLAPPABLE #-}
(Peekable a, Peekable b, Peekable c, Peekable d,
Peekable e, Peekable f, Peekable g) =>
Peekable (a, b, c, d, e, f, g)
where
peek = typeChecked "table" istable $ \idx ->
(,,,,,,) <$> nthValue idx 1 <*> nthValue idx 2 <*> nthValue idx 3
<*> nthValue idx 4 <*> nthValue idx 5 <*> nthValue idx 6
<*> nthValue idx 7
instance {-# OVERLAPPABLE #-}
(Peekable a, Peekable b, Peekable c, Peekable d,
Peekable e, Peekable f, Peekable g, Peekable h) =>
Peekable (a, b, c, d, e, f, g, h)
where
peek = typeChecked "table" istable $ \idx ->
(,,,,,,,) <$> nthValue idx 1 <*> nthValue idx 2 <*> nthValue idx 3
<*> nthValue idx 4 <*> nthValue idx 5 <*> nthValue idx 6
<*> nthValue idx 7 <*> nthValue idx 8
-- | Helper function to get the nth table value
nthValue :: (PeekError e, Peekable a)
=> StackIndex -> Lua.Integer -> LuaE e a
nthValue idx n = do
_ <- rawgeti idx n
peek top `Catch.finally` pop 1