@@ -1,8 +1,7 @@
1−{-# LANGUAGE ForeignFunctionInterface
2− , TypeSynonymInstances
3− , FlexibleInstances
4− , DeriveDataTypeable
5− , ScopedTypeVariables #-}
1+{-# LANGUAGE ForeignFunctionInterface #-}
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE TypeSynonymInstances #-}
4+{-# LANGUAGE ScopedTypeVariables #-}
65-----------------------------------------------------------------------------
76--
87-- Module : Language.Javascript.JSC.Object
@@ -11,39 +10,74 @@
1110--
1211-- Maintainer : Hamish Mackenzie <Hamish.K.Mackenzie@googlemail.com>
1312--
14−-- |
13+-- | Interface to JavaScript object
1514--
1615-----------------------------------------------------------------------------
1716
1817module Language.Javascript.JSC.Object (
19− objSetProperty
20− , objGetProperty
21− , objCallAsFunction
18+ JSObjectRef
19+ , MakeObjectRef(..)
20+
21+ -- * Property lookup
2222 , (!)
2323 , (!!)
24− , (#)
24+
25+ -- * Setting the value of a property
2526 , (<#)
27+
28+ -- * Calling JavaSctipt
29+ , (#)
2630 , new
2731 , call
32+
33+ -- * Calling Haskell From JavaScript
2834 , function
2935 , fun
36+ , JSCallAsFunction(..)
37+ -- ** Object Constructors
38+
39+ -- | There is no good way to support calling haskell code as a JavaScript
40+ -- constructor for the same reason that the return type of
41+ -- 'JSCallAsFunction' is 'JSUndefined'.
42+ --
43+ -- Instead of writing a constructor in Haskell write a function
44+ -- that takes a continuation. Create the JavaScript object
45+ -- and pass it to the continuation.
46+
47+ -- * Arrays
3048 , array
49+
50+ -- * Global Object
3151 , global
52+
53+ -- * Enumerating Properties
54+ , copyPropertyNames
55+ , propertyNames
56+
57+ -- * Low level
58+ , objCallAsFunction
59+ , objCallAsConstructor
3260) where
3361
3462import Prelude hiding ((!!))
3563import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSBase
36− (JSStringRef, JSObjectRef, JSValueRefRef, JSValueRef, JSContextRef)
37−import Foreign.C (CUInt, CULong(..))
64+ (JSPropertyNameArrayRef, JSStringRef, JSObjectRef, JSValueRefRef,
65+ JSValueRef, JSContextRef)
66+import Foreign.C (CSize(..), CUInt(..), CULong(..))
3867import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSObjectRef
39− (jsobjectsetpropertyatindex, jsobjectgetpropertyatindex,
40− jsobjectcallasconstructor, jsobjectmakearray,
41− jsobjectcallasfunction, jsobjectgetproperty, jsobjectsetproperty,
42− JSPropertyAttributes, JSObjectCallAsFunctionCallback,
43− jsobjectmakefunctionwithcallback)
68+ (jspropertynamearraygetnameatindex, jspropertynamearraygetcount,
69+ jsobjectcopypropertynames, jsobjectsetpropertyatindex,
70+ jsobjectgetpropertyatindex, jsobjectcallasconstructor,
71+ jsobjectmakearray, jsobjectcallasfunction, jsobjectgetproperty,
72+ jsobjectsetproperty, JSPropertyAttributes,
73+ JSObjectCallAsFunctionCallback, jsobjectmakefunctionwithcallback)
74+import Language.Javascript.JSC.Exception (rethrow)
4475import Language.Javascript.JSC.Value
45− (valMakeUndefined, valToObject, MakeValueRef(..),
46− MakeStringRef(..), MakeArgRefs(..), rethrow)
76+ (JSUndefined, valMakeUndefined, valToObject)
77+import Language.Javascript.JSC.PropRef (JSPropRef(..))
78+import Language.Javascript.JSC.Classes
79+ (MakeValueRef(..), MakeStringRef(..), MakeArgRefs(..), MakePropRef(..),
80+ MakeObjectRef(..))
4781import Language.Javascript.JSC.Monad
4882 (JSC)
4983import Control.Monad.Trans.Reader (runReaderT, ask)
@@ -57,154 +91,53 @@ import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSValueRef
5791 (jsvaluemakeundefined)
5892import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSContextRef
5993 (jscontextgetglobalobject)
94+import Language.Javascript.JSC.Properties
6095
61−---- Object Referene ----
62−class MakeObjectRef this where
63− makeObjectRef :: this -> JSC JSObjectRef
64−
96+-- | If we already have a JSObjectRef we are fine
6597instance MakeObjectRef JSObjectRef where
6698 makeObjectRef = return
6799
100+-- | JSObjectRef can be made by evaluating a function in 'JSC' as long
101+-- as it returns something we can make into a JSObjectRef.
68102instance MakeObjectRef v => MakeObjectRef (JSC v) where
69− makeObjectRef v = v >>= makeObjectRef
70−
71−---- Property References ----
72−data JSPropRef = JSPropRef JSObjectRef JSStringRef
73− | JSPropIndexRef JSObjectRef CUInt
74−
75−class MakePropRef this where
76− makePropRef :: this -> JSC JSPropRef
77−
78−instance MakePropRef JSPropRef where
79− makePropRef = return
80−
81−instance MakePropRef prop => MakePropRef (JSC prop) where
82− makePropRef prop = prop >>= makePropRef
83−
84−instance MakeObjectRef JSPropRef where
85− makeObjectRef = objGetProperty
86−
87−instance MakeValueRef JSPropRef where
88− makeValueRef = objGetProperty
89−
90−instance MakeArgRefs JSPropRef where
91− makeArgRefs p = do
92− rarg <- objGetProperty p
93− return [rarg]
94−
95−---- String global scope ----
96−instance MakePropRef String where
97− makePropRef name = do
98− this <- global
99− rname <- makeStringRef name
100− return (JSPropRef this rname)
101−
102−instance MakeObjectRef String where
103− makeObjectRef name = do
104− this <- global
105− rname <- makeStringRef name
106− (rethrow $ objGetPropertyByName this rname) >>= valToObject
103+ makeObjectRef v = v >>= makeObjectRef
107104
108−global :: JSC JSObjectRef
109−global = ask >>= (liftIO . jscontextgetglobalobject)
110−
111−objSetPropertyByName :: (MakeStringRef name, MakeValueRef val)
112− => JSObjectRef
113− -> name
114− -> val
115− -> JSPropertyAttributes
116− -> JSValueRefRef
117− -> JSC ()
118−objSetPropertyByName this name val attributes exceptions = do
119− gctxt <- ask
120− sName <- makeStringRef name
121− vref <- makeValueRef val
122− liftIO $ jsobjectsetproperty gctxt this sName vref attributes exceptions
123−
124−objSetPropertyAtIndex :: (MakeValueRef val)
125− => JSObjectRef
126− -> CUInt
127− -> val
128− -> JSValueRefRef
129− -> JSC ()
130−objSetPropertyAtIndex this index val exceptions = do
131− gctxt <- ask
132− vref <- makeValueRef val
133− liftIO $ jsobjectsetpropertyatindex gctxt this index vref exceptions
134−
135−objSetProperty :: (MakeValueRef val)
136− => JSPropRef
137− -> val
138− -> JSC ()
139−objSetProperty (JSPropRef this name ) val = rethrow $ objSetPropertyByName this name val 0
140−objSetProperty (JSPropIndexRef this index) val = rethrow $ objSetPropertyAtIndex this index val
141−
142−objGetPropertyByName :: MakeStringRef name
143− => JSObjectRef
144− -> name
145− -> JSValueRefRef
146− -> JSC JSValueRef
147−objGetPropertyByName this name exceptions = do
148− gctxt <- ask
149− sName <- makeStringRef name
150− liftIO $ jsobjectgetproperty gctxt this sName exceptions
151−
152−objGetPropertyAtIndex :: JSObjectRef
153− -> CUInt
154− -> JSValueRefRef
155− -> JSC JSValueRef
156−objGetPropertyAtIndex this index exceptions = do
157− gctxt <- ask
158− liftIO $ jsobjectgetpropertyatindex gctxt this index exceptions
159−
160−objGetProperty :: JSPropRef
161− -> JSC JSValueRef
162−objGetProperty (JSPropRef this name ) = rethrow $ objGetPropertyByName this name
163−objGetProperty (JSPropIndexRef this index) = rethrow $ objGetPropertyAtIndex this index
164−
165−objGetProperty' :: JSPropRef
166− -> JSC (JSObjectRef, JSValueRef)
167−objGetProperty' (JSPropRef this name) = do
168− p <- rethrow $ objGetPropertyByName this name
169− return (this, p)
170−objGetProperty' (JSPropIndexRef this index) = do
171− p <- rethrow $ objGetPropertyAtIndex this index
172− return (this, p)
173−
174−objCallAsFunction :: MakeArgRefs args
175− => JSObjectRef
176− -> JSObjectRef
177− -> args
178− -> JSValueRefRef
179− -> JSC JSValueRef
180−objCallAsFunction function this args exceptions = do
181− gctxt <- ask
182− rargs <- makeArgRefs args
183− liftIO $ withArrayLen rargs $ \ largs pargs ->
184− jsobjectcallasfunction gctxt function this (fromIntegral largs) pargs exceptions
185−
186−objCallAsConstructor :: MakeArgRefs args
187− => JSObjectRef
188− -> args
189− -> JSValueRefRef
190− -> JSC JSValueRef
191−objCallAsConstructor function args exceptions = do
192− gctxt <- ask
193− rargs <- makeArgRefs args
194− liftIO $ withArrayLen rargs $ \ largs pargs ->
195− jsobjectcallasconstructor gctxt function (fromIntegral largs) pargs exceptions
196−
197−(!) :: (MakeObjectRef this, MakeStringRef name) => this -> name -> JSC JSPropRef
105+-- | Lookup a property based on its name. This function just constructs a JSPropRef
106+-- the lookup is delayed until we use the JSPropRef. This makes it a bit lazy compared
107+-- to JavaScript's @.@ operator.
108+--
109+-- >>> runjs $ eval "'Hello World'.length"
110+-- >>> runjs $ val "Hello World" ! "length"
111+-- "11"
112+(!) :: (MakeObjectRef this, MakeStringRef name)
113+ => this -- ^ Object to look on
114+ -> name -- ^ Name of the property to find
115+ -> JSC JSPropRef -- ^ Property reference
198116this ! name = do
199117 rthis <- makeObjectRef this
200118 rname <- makeStringRef name
201119 return (JSPropRef rthis rname)
202120
203−(!!) :: (MakeObjectRef this) => this -> CUInt -> JSC JSPropRef
121+-- | Lookup a property based on its index. This function just constructs a JSPropRef
122+-- the lookup is delayed until we use the JSPropRef. This makes it a bit lazy compared
123+-- to JavaScript's @[]@ operator.
124+--
125+-- >>> runjs $ eval "'Hello World'[6]"
126+-- >>> runjs $ val "Hello World" !! 6
127+-- "W"
128+(!!) :: (MakeObjectRef this)
129+ => this -- ^ Object to look on
130+ -> CUInt -- ^ Index of the property to lookup
131+ -> JSC JSPropRef -- ^ Property reference
204132this !! index = do
205133 rthis <- makeObjectRef this
206134 return (JSPropIndexRef rthis index)
207135
136+-- | Call a JavaScript function
137+--
138+-- >>> runjs $ eval "'Hello World'.indexOf('World')"
139+-- >>> runjs $ val "Hello World" ! "indexOf" # ["World"]
140+-- "6"
208141(#) :: (MakePropRef prop, MakeArgRefs args)
209142 => prop -> args -> JSC JSValueRef
210143prop # args = do
@@ -212,20 +145,38 @@ prop # args = do
212145 (this, f) <- objGetProperty' rprop
213146 rethrow $ objCallAsFunction f this args
214147
148+-- | Call a JavaScript function
149+--
150+-- >>> runjs $ eval "var j = {}; j.x = 1; j.x"
151+-- >>> runjs $ do {j <- eval "({})"; j!"x" <# 1; j!"x"}
152+-- "1"
215153infixr 0 <#
216154(<#) :: (MakePropRef prop, MakeValueRef val)
217− => prop -> val -> JSC JSPropRef
155+ => prop -- ^ Property to set
156+ -> val -- ^ Value to set it to
157+ -> JSC JSPropRef -- ^ Reference to the property set
218158prop <# val = do
219159 p <- makePropRef prop
220160 objSetProperty p val
221161 return p
222162
163+-- | Use this to create a new JavaScript object
164+--
165+-- >>> runjs $ new "Date" (2013, 1, 1)
166+-- "Fri Feb 01 2013 00:00:00 GMT+1300 (NZDT)"
223167new :: (MakeObjectRef constructor, MakeArgRefs args)
224− => constructor -> args -> JSC JSValueRef
168+ => constructor
169+ -> args
170+ -> JSC JSValueRef
225171new constructor args = do
226172 f <- makeObjectRef constructor
227173 rethrow $ objCallAsConstructor f args
228174
175+-- | Call function with a given @this@. In most cases you should use '#'.
176+--
177+-- >>> runjs $ eval "(function(){return this;}).apply('Hello', [])"
178+-- >>> runjs $ do { test <- eval "(function(){return this;})"; call test (val "Hello") () }
179+-- "Hello"
229180call :: (MakeObjectRef function, MakeObjectRef this, MakeArgRefs args)
230181 => function -> this -> args -> JSC JSValueRef
231182call function this args = do
@@ -242,15 +193,35 @@ type JSObjectCallAsFunctionCallback' =
242193 -> JSValueRefRef
243194 -> IO JSValueRef
244195
245−type JSCallAsFunction = JSValueRef -> JSValueRef -> [JSValueRef] -> JSC ()
196+-- | Type used for Haskell functions called from JavaScript.
197+type JSCallAsFunction = JSValueRef -- ^ Function object
198+ -> JSValueRef -- ^ this
199+ -> [JSValueRef] -- ^ Function arguments
200+ -> JSC JSUndefined -- ^ Only 'JSUndefined' can be returned because
201+ -- the function may need to be executed in a
202+ -- different thread. If you need to get a
203+ -- value out pass in a continuation function
204+ -- as an argument and invoke it from haskell.
205+
206+-- | Short hand @::JSCallAsFunction@ so a haskell function can be passed to
207+-- a to a JavaScipt one.
208+--
209+-- >>> runjs $ eval "(function(f) {f('Hello');})(function (a) {console.log(a)})"
210+-- >>> runjs $ call (eval "(function(f) {f('Hello');})") global [fun $ \ _ _ args -> valToText (head args) >>= (liftIO . print) ]
211+-- "Hello"
212+-- "undefined"
213+fun :: JSCallAsFunction -> JSCallAsFunction
214+fun = id
246215
247216foreign import ccall "wrapper"
248217 mkJSObjectCallAsFunctionCallback :: JSObjectCallAsFunctionCallback' -> IO JSObjectCallAsFunctionCallback
249218
219+-- ^ Make a JavaScript function object that wraps a Haskell function.
250220function :: MakeStringRef name
251− => name
252− -> JSCallAsFunction
253− -> JSC JSObjectRef
221+ => name -- ^ Name of the function
222+ -> JSCallAsFunction -- ^ Haskell function to call
223+ -> JSC JSObjectRef -- ^ Returns a JavaScript function object that will
224+ -- call the Haskell one when it is called
254225function name f = do
255226 gctxt <- ask
256227 callback <- liftIO $ mkJSObjectCallAsFunctionCallback wrap
@@ -259,18 +230,18 @@ function name f = do
259230 where
260231 wrap ctx fobj this argc argv exception = do
261232 args <- peekArray (fromIntegral argc) argv
262− flip runReaderT ctx $ do
233+ (`runReaderT` ctx) $
263234 f fobj this args >>= makeValueRef
264235 `E.catch` \(e :: SomeException) -> do
265236 str <- runReaderT (makeValueRef . T.pack $ show e) ctx
266237 poke exception str
267238 jsvaluemakeundefined ctx
268239
269−fun :: JSCallAsFunction -> JSCallAsFunction
270−fun f = f
271−
240+-- | A callback to Haskell can be used as a JavaScript value. This will create
241+-- an anonymous JavaScript function object. Use 'function' to create one with
242+-- a name.
272243instance MakeValueRef JSCallAsFunction where
273− makeValueRef f = function (nullPtr::JSStringRef) f
244+ makeValueRef = function (nullPtr::JSStringRef)
274245
275246instance MakeArgRefs JSCallAsFunction where
276247 makeArgRefs f = do
@@ -284,25 +255,89 @@ makeArray args exceptions = do
284255 liftIO $ withArrayLen rargs $ \ len ptr ->
285256 jsobjectmakearray gctxt (fromIntegral len) ptr exceptions
286257
258+-- | Make an JavaScript array from a list of values
259+--
260+-- >>> runjs $ eval "['Hello', 'World'][1]"
261+-- >>> runjs $ array ["Hello", "World"] !! 1
262+-- "World"
263+-- >>> runjs $ eval "['Hello', null, undefined, true, 1]"
264+-- >>> runjs $ array ("Hello", JSNull, (), True, 1.0::Double)
265+-- "Hello,,,true,1"
287266array :: MakeArgRefs args => args -> JSC JSObjectRef
288267array = rethrow . makeArray
289268
269+-- | JavaScript's global object
270+global :: JSC JSObjectRef
271+global = ask >>= (liftIO . jscontextgetglobalobject)
290272
273+---- String global scope ----
274+-- | A string on its own is assumed to be the name of a property in the global object
275+instance MakePropRef String where
276+ makePropRef name = do
277+ this <- global
278+ rname <- makeStringRef name
279+ return (JSPropRef this rname)
291280
281+-- | A string on its own is assumed to be the name of a property in the global object
282+instance MakeObjectRef String where
283+ makeObjectRef name = do
284+ this <- global
285+ rname <- makeStringRef name
286+ rethrow (objGetPropertyByName this rname) >>= valToObject
292287
288+-- | Get an array containing the property names present on a given object
289+copyPropertyNames :: MakeObjectRef this => this -> JSC JSPropertyNameArrayRef
290+copyPropertyNames this = do
291+ gctxt <- ask
292+ rthis <- makeObjectRef this
293+ liftIO $ jsobjectcopypropertynames gctxt rthis
293294
295+-- | Get the number of names in a property name array
296+propertyNamesCount :: MonadIO m => JSPropertyNameArrayRef -> m CSize
297+propertyNamesCount names = liftIO $ jspropertynamearraygetcount names
294298
299+-- | Get a name out of a property name array
300+propertyNamesAt :: MonadIO m => JSPropertyNameArrayRef -> CSize -> m JSStringRef
301+propertyNamesAt names index = liftIO $ jspropertynamearraygetnameatindex names index
295302
303+-- | Convert property array to a list
304+propertyNamesList :: MonadIO m => JSPropertyNameArrayRef -> m [JSStringRef]
305+propertyNamesList names = do
306+ count <- propertyNamesCount names
307+ mapM (propertyNamesAt names) $ enumFromTo 0 (count - 1)
296308
309+-- | Get a list containing the property names present on a given object
310+propertyNames :: MakeObjectRef this => this -> JSC [JSStringRef]
311+propertyNames this = copyPropertyNames this >>= propertyNamesList
297312
313+-- | Get a list containing references to all the properties present on a given object
314+properties :: MakeObjectRef this => this -> JSC [JSPropRef]
315+properties this = propertyNames this >>= mapM (this !)
298316
317+-- | Call a JavaScript object as function. Consider using '#'.
318+objCallAsFunction :: MakeArgRefs args
319+ => JSObjectRef
320+ -> JSObjectRef
321+ -> args
322+ -> JSValueRefRef
323+ -> JSC JSValueRef
324+objCallAsFunction function this args exceptions = do
325+ gctxt <- ask
326+ rargs <- makeArgRefs args
327+ liftIO $ withArrayLen rargs $ \ largs pargs ->
328+ jsobjectcallasfunction gctxt function this (fromIntegral largs) pargs exceptions
299329
300−
301−
302−
303−
304−
305−
330+-- | Call a JavaScript object as a constructor. Consider using 'new'.
331+objCallAsConstructor :: MakeArgRefs args
332+ => JSObjectRef
333+ -> args
334+ -> JSValueRefRef
335+ -> JSC JSValueRef
336+objCallAsConstructor function args exceptions = do
337+ gctxt <- ask
338+ rargs <- makeArgRefs args
339+ liftIO $ withArrayLen rargs $ \ largs pargs ->
340+ jsobjectcallasconstructor gctxt function (fromIntegral largs) pargs exceptions
306341
307342
308343