haste-compiler 0.2.99 → 0.3
raw patch · 39 files changed
+731/−901 lines, 39 filesdep ~ghcdep ~shellmate
Dependency ranges changed: ghc, shellmate
Files
- haste-compiler.cabal +7/−16
- lib/rts.js +8/−0
- lib/stdlib.js +13/−3
- libraries/fursuit/src/FRP/Fursuit.hs +0/−106
- libraries/fursuit/src/FRP/Fursuit/Async.hs +0/−33
- libraries/fursuit/src/FRP/Fursuit/Locking.hs +0/−33
- libraries/fursuit/src/FRP/Fursuit/Pipe.hs +0/−32
- libraries/fursuit/src/FRP/Fursuit/Signal.hs +0/−175
- libraries/fursuit/src/FRP/Fursuit/Sink.hs +0/−26
- libraries/haste-lib/src/Haste.hs +2/−2
- libraries/haste-lib/src/Haste/App.hs +3/−3
- libraries/haste-lib/src/Haste/App/Client.hs +30/−21
- libraries/haste-lib/src/Haste/App/Concurrent.hs +13/−64
- libraries/haste-lib/src/Haste/App/Monad.hs +64/−37
- libraries/haste-lib/src/Haste/Binary.hs +9/−5
- libraries/haste-lib/src/Haste/Binary/Types.hs +10/−4
- libraries/haste-lib/src/Haste/Concurrent.hs +63/−22
- libraries/haste-lib/src/Haste/Concurrent/Monad.hs +13/−2
- libraries/haste-lib/src/Haste/DOM.hs +30/−2
- libraries/haste-lib/src/Haste/Foreign.hs +161/−37
- libraries/haste-lib/src/Haste/Graphics/Canvas.hs +7/−1
- libraries/haste-lib/src/Haste/Hash.hs +47/−13
- libraries/haste-lib/src/Haste/JSON.hs +12/−1
- libraries/haste-lib/src/Haste/Random.hs +46/−31
- libraries/haste-lib/src/Haste/Reactive.hs +0/−4
- libraries/haste-lib/src/Haste/Reactive/Ajax.hs +0/−34
- libraries/haste-lib/src/Haste/Reactive/DOM.hs +0/−93
- libraries/haste-lib/src/Haste/WebSockets.hs +16/−10
- src/ArgSpecs.hs +15/−2
- src/Data/JSTarget/Constructors.hs +1/−6
- src/Data/JSTarget/Optimize.hs +13/−7
- src/Haste/CodeGen.hs +21/−12
- src/Haste/Config.hs +20/−5
- src/Haste/Linker.hs +1/−1
- src/Haste/PrimOps.hs +1/−2
- src/Haste/Util.hs +0/−13
- src/Haste/Version.hs +1/−1
- src/Main.hs +44/−11
- src/haste-boot.hs +60/−31
haste-compiler.cabal view
@@ -1,5 +1,5 @@ Name: haste-compiler-Version: 0.2.99+Version: 0.3 License: BSD3 License-File: LICENSE Synopsis: Haskell To ECMAScript compiler@@ -78,7 +78,7 @@ network, HTTP, executable-path,- shellmate >= 0.1.3+ shellmate >= 0.1.5 Default-Language: Haskell98 Executable hastec@@ -105,14 +105,14 @@ process, random, system-fileio,- executable-path+ executable-path,+ shellmate >= 0.1.5 Main-Is: Main.hs Other-Modules: Args ArgSpecs Haste- Haste.Util Haste.Version Haste.Environment Haste.Config@@ -197,15 +197,13 @@ time, transformers, executable-path,- shellmate+ shellmate >= 0.1.5 default-language: Haskell98 Library- Hs-Source-Dirs: libraries/haste-lib/src, libraries/fursuit/src, src+ Hs-Source-Dirs: libraries/haste-lib/src, src GHC-Options: -Wall -O2 Exposed-Modules:- FRP.Fursuit- FRP.Fursuit.Async Haste Haste.App Haste.App.Concurrent@@ -213,7 +211,6 @@ Haste.Compiler Haste.JSON Haste.Ajax- Haste.Reactive Haste.DOM Haste.Prim Haste.Concurrent@@ -224,10 +221,6 @@ Haste.WebSockets Haste.LocalStorage Other-Modules:- FRP.Fursuit.Signal- FRP.Fursuit.Sink- FRP.Fursuit.Pipe- FRP.Fursuit.Locking Haste.JSType Haste.Callback Haste.Compiler.Flags@@ -235,8 +228,6 @@ Paths_haste_compiler Haste.Hash Haste.Random- Haste.Reactive.DOM- Haste.Reactive.Ajax Haste.Concurrent.Monad Haste.Concurrent.Ajax Haste.App.Client@@ -261,7 +252,7 @@ websockets >= 0.8, utf8-string, -- For Haste.Compiler- shellmate >= 0.1.3,+ shellmate >= 0.1.5, data-default, directory, executable-path,
lib/rts.js view
@@ -166,6 +166,14 @@ return [0, sign, manHigh, manLow, exp]; } +function isFloatFinite(x) {+ return isFinite(x);+}++function isDoubleFinite(x) {+ return isFinite(x);+}+ function err(str) { die(toJSStr(str)); }
lib/stdlib.js view
@@ -54,7 +54,8 @@ posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; }- return [posx - e.target.offsetLeft, posy - e.target.offsetTop];+ return [posx - (e.target.offsetLeft || 0),+ posy - (e.target.offsetTop || 0)]; } function jsSetCB(elem, evt, cb) {@@ -233,6 +234,8 @@ return arr.join(sep); } +var jsJSONParse = JSON.parse;+ // JSON stringify a string function jsStringify(str) { return JSON.stringify(str);@@ -266,7 +269,7 @@ break; case 'object': if(obj instanceof Array) {- return [3, arr2lst(obj, 0)];+ return [3, arr2lst_json(obj, 0)]; } else { // Object type but not array - it's a dictionary. // The RFC doesn't say anything about the ordering of keys, but@@ -287,11 +290,18 @@ } } +function arr2lst_json(arr, elem) {+ if(elem >= arr.length) {+ return [0];+ }+ return [1, toHS(arr[elem]), new T(function() {return arr2lst_json(arr,elem+1);})]+}+ function arr2lst(arr, elem) { if(elem >= arr.length) { return [0]; }- return [1, toHS(arr[elem]), new T(function() {return arr2lst(arr,elem+1);})]+ return [1, arr[elem], new T(function() {return arr2lst(arr,elem+1);})] } function lst2arr(xs) {
− libraries/fursuit/src/FRP/Fursuit.hs
@@ -1,106 +0,0 @@--- | Oversimplified FRP library, using no concurrency primitives.--- As it's primarily intended to be used with haste-compiler, compiled to--- Javascript, this library is NOT thread-safe, though it could be made so--- by simply slamming a global lock around the write and newSinkID--- operations.-module FRP.Fursuit (module Sink, module Pipe, Signal, sink, new, union, accumS,- filterS, whenS, zipS, untilS, fromS, stateful, unions,- filterMapS) where-import FRP.Fursuit.Signal-import FRP.Fursuit.Pipe as Pipe-import FRP.Fursuit.Sink as Sink-import System.IO.Unsafe-import Control.Applicative-import Data.List (foldl1')-import Data.Maybe (fromJust, isJust)---- | Execute the specified IO action to obtain a new signal when registering--- signals. This is handy when you're creating a signal from an external--- event for use with a single sink:--- @--- clicked <- buttonSig "my_button"--- sink (_ -> putStrLn "Button clicked!") clicked--- -- ...can be rewritten as:--- sink (_ -> putStrLn "Button clicked!") (new $ buttonSig "my_button")--- @-{-# NOINLINE new #-}-new :: IO (Signal a) -> Signal a-new = New . unsafePerformIO---- | Create a signal that has the value of whichever parent signal fired last.--- The union is left biased.-union :: Signal a -> Signal a -> Signal a-union = Union---- | The union of n > 1 signals.-unions :: [Signal a] -> Signal a-unions = foldl1' union---- | Behaves pretty much like scanl on signals. Initialize the accumulator with--- a default value; every time the function signal triggers, apply the--- function to the accumulator and pass on the result.-accumS :: a -> Signal (a -> a) -> Signal a-accumS = Accum---- | Create a signal that keeps local state but returns another value.-stateful :: (st, a) -> Signal (st -> (st, a)) -> Signal a-stateful initial f =- snd <$> accumS initial (acc <$> f)- where- acc fun (st, _) = fun st---- | Filter out events. filterS pred sig only lets the signal sig through if--- it fulfills the predicate pred. For example:--- @--- (pa, a) <- pipe (0 :: Int)--- (pb, b) <- pipe (0 :: Int)--- let plus = (+) <$> filterS (< 10) a <*> b--- sink (putStrLn . show) plus--- write pa 20--- write pb 20--- write pa 5--- @--- The above code will print 20 and 25; writing 20 to pa gets filtered out,--- as 20 does not fulfull (< 10) so no signal is fired. b isn't so filtered--- however, so the 20 goes through just fine, and is added to the last good--- value of a (which is 0 - its initial value). The final 5 does fulfill--- (< 10), so the signal goes through and we get 25.-filterS :: (a -> Bool) -> Signal a -> Signal a-filterS = Filter---- | Combined fmap and filter.-filterMapS :: (a -> Maybe b) -> Signal a -> Signal b-filterMapS f sig =- fromJust <$> filterS isJust (f <$> sig)--{-# RULES-"filterS/filterS" forall p1 p2 sig.- filterS p1 (filterS p2 sig) = filterS (\x -> p1 x && p2 x) sig- #-}---- | Only allow a signal to pass through when the time varying value is true.-whenS :: Signal Bool -> Signal a -> Signal a-whenS p s = snd <$> (filterS fst $ zipS p s)---- | Signal equivalent of the list function by the same name.-zipS :: Signal a -> Signal b -> Signal (a, b)-zipS a b = (,) <$> a <*> b---- | Pass through a signal as long as it does not fulfill a predicate. From the--- point when it does fulfill that predicate, the signal never propagates--- again unless the reset signal fired.-untilS :: (a -> Bool) -> Signal a -> Signal b -> Signal a-untilS p sig reset =- snd <$> filterS fst (zipS propagate sig)- where- propagate = accumS True update- update = (const False <$ filterS p sig) `union` (const True <$ reset)---- | Don't pass the signal through until it fulfills a predicate. After the--- predicate has been fulfilled at least once, always propagate the signal.-fromS :: (a -> Bool) -> Signal a -> Signal b -> Signal a-fromS p sig reset =- snd <$> filterS fst (zipS propagate sig)- where- propagate = accumS False update- update = (const True <$ filterS p sig) `union` (const False <$ reset)
− libraries/fursuit/src/FRP/Fursuit/Async.hs
@@ -1,33 +0,0 @@-module FRP.Fursuit.Async where-import Control.Applicative-import FRP.Fursuit.Signal-import FRP.Fursuit.Sink-import FRP.Fursuit.Pipe---- | Create an asynchronous signal. When the signal is triggered, it should--- arrange for a value to written into its obtained pipe at some point in--- the future; when that happens, the signal continues on its path.------ The signal setup action is guaranteed to be called exactly once per--- received signal; thus the following code will print 1 two times rather--- than 1 followed by 2:--- @--- ref <- newIORef ()--- (p, s) <- pipe ()--- sig <- async $ (\p -> modifyIORef ref (+1) >> write p ref) <$ s--- sink (\r -> readIORef r >>= print) sig--- sink (\r -> readIORef r >>= print) sig--- write p ()--- @--- Async is primarily intended as a building block for other more high level--- signals, and should be used with extreme care as it permits arbitrary side--- effects to happen in the course of propagating a signal.------ It's also worth noting that triggering the signal returned by async does--- not trigger the setup signal, but will instead use the last value held by--- the output signal, if any.-async :: Signal (Pipe a -> IO ()) -> IO (Signal a)-async setup = do- (p,s) <- emptyPipe- perform $ setup <*> pure p- return s
− libraries/fursuit/src/FRP/Fursuit/Locking.hs
@@ -1,33 +0,0 @@-{-# LANGUAGE CPP #-}--- | Locking for Fursuit - to enable running it safely on multithreaded--- platforms.-module FRP.Fursuit.Locking (withFursuitLock) where--#ifdef __HASTE__---- | The Haste version is just a no-op; no threads in the browser!-withFursuitLock :: IO a -> IO a-withFursuitLock = id--#else--import Control.Concurrent.MVar-import System.IO.Unsafe--{-# NOINLINE fursuitLock #-}--- | While this may seem insanely unsafe due to blackholing, it's actually--- perfectly OK as of GHC 7.4.2, when CAFs were made atomic.--- (http://hackage.haskell.org/trac/ghc/ticket/5558)-fursuitLock :: MVar ()-fursuitLock = unsafePerformIO $! newMVar ()---- | The GHC version is really stupid: smack a big lock around the entire--- write operation. It works, but it's neither elegant nor efficient.-withFursuitLock :: IO a -> IO a-withFursuitLock action = do- _ <- takeMVar fursuitLock- res <- action- putMVar fursuitLock ()- return res--#endif
− libraries/fursuit/src/FRP/Fursuit/Pipe.hs
@@ -1,32 +0,0 @@-{-# LANGUAGE BangPatterns, CPP #-}--- | Pipes are the only way to cause a 'Signal' to fire from the outside.-module FRP.Fursuit.Pipe (Pipe, pipe, emptyPipe, write) where-import Data.IORef-import FRP.Fursuit.Signal-import qualified Data.IntMap as M-import FRP.Fursuit.Locking---- | Create a pipe. Writing to a pipe is the only way to manually trigger a--- signal.-emptyPipe :: IO (Pipe a, Signal a)-emptyPipe = do- ref <- newIORef Nothing- orig <- newIORef False- sinks <- newIORef M.empty- return (P ref sinks orig, Pipe ref sinks orig)---- | Create a pipe with an initial value.-pipe :: a -> IO (Pipe a, Signal a)-pipe !initially = do- ps@(P ref _ _, _) <- emptyPipe- writeIORef ref (Just initially)- return ps---- | Write a value into a pipe. This will cause the pipe's associated signal--- to fire.-write :: Pipe a -> a -> IO ()-write (P value listeners origin) !x = withFursuitLock $ do- writeIORef origin True- writeIORef value (Just x)- readIORef listeners >>= M.foldl' (>>) (return ())- writeIORef origin False
− libraries/fursuit/src/FRP/Fursuit/Signal.hs
@@ -1,175 +0,0 @@-{-# LANGUAGE GADTs, BangPatterns, TupleSections #-}-{-# OPTIONS_GHC -fno-warn-unused-binds #-}-module FRP.Fursuit.Signal (Signal (..), Pipe (..), sink) where-import Data.IORef-import System.IO.Unsafe-import Control.Applicative-import qualified Data.IntMap as M-import Data.Maybe--type SinkID = Int-type Origin = Bool-type Sig a = IO (Maybe (a, Origin))-type SinkList = M.IntMap (IO ())--data Pipe a = P {- piperef :: IORef (Maybe a),- cbref :: IORef (M.IntMap (IO ())),- origref :: IORef Origin- }---- New is implemented as a thin wrapper around unsafePerformIO, to make sure--- it's only evaluated once and never outside of sink.-data Signal a where- App :: Signal (a -> b) -> Signal a -> Signal b- Pure :: a -> Signal a- Pipe :: IORef (Maybe a) -> IORef SinkList -> IORef Origin -> Signal a- Filter :: (a -> Bool) -> Signal a -> Signal a- Accum :: a -> Signal (a -> a) -> Signal a- New :: Signal a -> Signal a- Union :: Signal a -> Signal a -> Signal a--{-# NOINLINE sinkIDs #-}-sinkIDs :: IORef SinkID-sinkIDs = unsafePerformIO $ newIORef 0---- | Generate a new sink ID, and update the global list of such IDs.-newSinkID :: IO SinkID-newSinkID = do- sid <- readIORef sinkIDs- writeIORef sinkIDs $! sid+1- return sid---- | Attach a signal to an actuator.-sink :: (a -> IO ()) -> Signal a -> IO ()-sink act sig = do- sig' <- compile sig >>= return . mkSnk act- sid <- newSinkID- mapM_ (\sinks -> modifyIORef sinks (M.insert sid sig')) (sources sig)- where- mkSnk action signal = do- result <- signal- case result of- Just (val, actuallyHappened) | actuallyHappened -> action val- _ -> return ()-- -- Find all sources for this signal- sources :: Signal a -> [IORef SinkList]- sources (App f x) = sources f ++ sources x- sources (Pure _) = []- sources (Pipe _ src _) = [src]- sources (Filter _ s) = sources s- sources (Accum _ s) = sources s- sources (New s) = sources s- sources (Union a b) = sources a ++ sources b- - -- Compile the signal into an IO action we can trigger whenever one of its- -- sources gets a signal.- compile :: Signal a -> IO (Sig a)- compile (App sf sx) = do- f <- compile sf- x <- compile sx- return (f `appS` x)- compile (Pure x) = do- return (return $ Just (x, False))- compile (Pipe value _ origin) = do- return $ do- mval <- readIORef value- orig <- readIORef origin- return $ mval >>= \val -> return (val, orig)- compile (Filter predicate s) = do- s' <- compile s- ms <- s'- lastGood <- case ms of- Just (initial, _) | predicate initial -> newIORef (Just initial)- _ -> newIORef Nothing- s'' <- compile s- return (fltS predicate lastGood s'')- compile (Accum initially s) = do- s' <- compile s- ref <- newIORef initially- return (accS ref s')- compile (New signal) = do- compile signal- compile (Union a b) = do- a' <- compile a- b' <- compile b- -- Prefer to initialize with the value of the left signal.- maval <- a'- initial <- case maval of- Just (x, _) -> return (Just x)- _ -> do- mbval <- b'- case mbval of- Just (x, _) -> return (Just x)- _ -> return Nothing- prev <- newIORef initial- return $ uniS a' b' prev---- | Union of two events. Both events are always evaluated.-uniS :: Sig a -> Sig a -> IORef (Maybe a) -> Sig a-uniS sa sb prevref = do- ma <- sa- mb <- sb- prev <- readIORef prevref- case listToMaybe $ filter snd $ catMaybes [ma, mb] of- Nothing -> do- return $ fmap (, False) prev- val -> do- writeIORef prevref (fmap fst val)- return val---- | Basically ap or <*>, but takes the origin indicator into account.-appS :: Sig (a -> b) -> Sig a -> Sig b-appS sf sx = do- mf <- sf- mx <- sx- return $ do- (f, origf) <- mf- (x, origx) <- mx- return (f x, origf || origx)---- | filterS, with a memo reference for the last value that passed through.-fltS :: (a -> Bool) -> IORef (Maybe a) -> Sig a -> Sig a-fltS predicate lastGood signal = do- msig <- signal- case msig of- Just (val, orig) -> do- if predicate val && orig- then do- writeIORef lastGood (Just val)- return $ Just (val, True)- else do- mlast <- readIORef lastGood- case mlast of- Just lastVal -> return $ Just (lastVal, False)- _ -> return Nothing- _ -> do- return Nothing---- | accumS-accS :: IORef a -> Sig (a -> a) -> Sig a-accS lastRef sf = do- mf <- sf- case mf of- Just (f, orig) -> do- if orig- then do- val <- readIORef lastRef- let !x = f val- writeIORef lastRef x- return $ Just (x, True)- else do- lastVal <- readIORef lastRef- return $ Just (lastVal, False)- Nothing -> do- -- Even if upstream can't compute its value, Accum can.- lastVal <- readIORef lastRef- return $ Just (lastVal, False)--instance Functor Signal where- fmap f x = pure f <*> x--instance Applicative Signal where- pure = Pure- (<*>) = App
− libraries/fursuit/src/FRP/Fursuit/Sink.hs
@@ -1,26 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}--- | Various kinds of sinks.-module FRP.Fursuit.Sink (Sink (..), perform) where-import Data.IORef-import FRP.Fursuit.Signal-import FRP.Fursuit.Pipe---- | Perform the IO action returned by a signal whenever triggered.-perform :: Signal (IO ()) -> IO ()-perform = sink id---- | Bind a signal to a value of some type. Examples of instances would be--- IORef, where ref << sig would store the value of sig in ref whenever--- triggered, or Pipe, where p << sig would write the value of sig to p.-class Sink s a where- (<<) :: s -> Signal a -> IO ()-infixl 0 <<--instance Sink (Pipe a) a where- p << s = sink (write p) s--instance Sink (IORef a) a where- ref << s = sink (writeIORef ref) s--instance Sink (a -> IO ()) a where- act << s = sink act s
libraries/haste-lib/src/Haste.hs view
@@ -41,8 +41,8 @@ return (fromJSStr a) -- | Javascript eval() function.-eval :: MonadIO m => String -> m String-eval js = liftIO $ jsEval (toJSStr js) >>= return . fromJSStr+eval :: MonadIO m => JSString -> m JSString+eval = liftIO . jsEval -- | Use console.log to write a message. writeLog :: MonadIO m => String -> m ()
libraries/haste-lib/src/Haste/App.hs view
@@ -3,10 +3,10 @@ -- functions are exported by both Haste and Haste.App with different -- definitions. For simplicity, import one or the other, not both. module Haste.App (- MonadIO, Exportable, App, Server, Useless, Export, Done,+ MonadIO, Remotable, App, Server, Remote, Done, Sessions, SessionID,- liftServerIO, forkServerIO, export, runApp,- (<.>), mkUseful, getSessionID, getActiveSessions, onSessionEnd,+ liftServerIO, forkServerIO, remote, runApp,+ (<.>), getSessionID, getActiveSessions, onSessionEnd, AppCfg, cfgURL, cfgPort, mkConfig, Client, runClient, onServer, liftIO,
libraries/haste-lib/src/Haste/App/Client.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE OverloadedStrings, TypeFamilies, MultiParamTypeClasses,- FlexibleInstances #-}+ FlexibleInstances, CPP #-} module Haste.App.Client ( Client, ClientState, runClient, onServer, liftCIO, get, runClientCIO@@ -16,20 +16,23 @@ import Data.IORef data ClientState = ClientState {- csWebSocket :: WebSocket,+ csSendBlob :: MVar (Blob -> Client ()), csNonce :: IORef Int, csResultVars :: IORef [(Int, MVar Blob)] } -initialState :: IORef Int -> IORef [(Int,MVar Blob)] -> WebSocket -> ClientState+initialState :: IORef Int+ -> IORef [(Int,MVar Blob)]+ -> MVar (Blob -> Client ())+ -> ClientState initialState n mv ws = ClientState {- csWebSocket = ws,+ csSendBlob = ws, csNonce = n, csResultVars = mv } --- | A client-side computation. See it as XHaste's version of the IO monad.+-- | A client-side computation. See it as Haste.App's version of the IO monad. newtype Client a = Client { unC :: ClientState -> CIO a }@@ -61,7 +64,7 @@ instance MonadBlob Client where getBlobData = liftCIO . getBlobData- getBlobText = liftCIO . getBlobText+ getBlobText' = liftCIO . getBlobText' -- | Lift a CIO action into the Client monad.@@ -86,16 +89,19 @@ runClient_ url (Client m) = concurrent $ do mv <- liftIO $ newIORef [] n <- liftIO $ newIORef 0- let errorhandler = error "WebSockets connection died for some reason!"- computation ws = m (initialState n mv ws)- withBinaryWebSocket url (handler mv) errorhandler computation+ let errhandler = error "WebSockets connection died for some reason!"+ openWS blob = do+ wsvar <- get csSendBlob+ liftCIO $ do+ _ <- takeMVar wsvar+ w <- withBinaryWebSocket url (handler mv) errhandler return+ putMVar wsvar (liftCIO . wsSendBlob w)+ wsSendBlob w blob+ ws <- newMVar openWS+ m (initialState n mv ws) where- -- When a message comes in, attempt to extract from it two members "nonce"- -- and "result". Find the result MVar corresponding to the nonce and write- -- the result to it, then discard the MVar.- -- TODO: if the nonce is not found, the result vars list if left as it was.- -- Maybe we should crash here instead, since this is clearly an- -- unrecoverable error?+ -- Find the result MVar corresponding to the nonce and write the result to+ -- it, then discard the MVar. handler rvars _ msg = do msg' <- getBlobData msg join . liftIO $ atomicModifyIORef rvars $ \vs ->@@ -124,17 +130,20 @@ runClientCIO cs (Client m) = m cs -- | Perform a server-side computation, blocking the client thread until said--- computation returns. All free variables in the server-side computation--- which originate in the Client monad must be serializable.-onServer :: Binary a => Export (Server a) -> Client a-onServer (Export cid args) = __call cid (reverse args)+-- computation returns.+onServer :: Binary a => Remote (Server a) -> Client a+#ifdef __HASTE__+onServer (Remote cid args) = __call cid (reverse args)+#else+onServer _ = undefined+#endif -- | Make a server-side call. __call :: Binary a => CallID -> [Blob] -> Client a __call cid args = do- ws <- get csWebSocket+ send <- get csSendBlob >>= liftCIO . readMVar (nonce, mv) <- newResult- liftCIO . wsSendBlob ws . encode $ ServerCall {+ send . encode $ ServerCall { scNonce = nonce, scMethod = cid, scArgs = args
libraries/haste-lib/src/Haste/App/Concurrent.hs view
@@ -6,27 +6,25 @@ -- This will likely be the state of Haste concurrency until Javascript gains -- decent native concurrency support. module Haste.App.Concurrent (- C.MVar, MBox, Send, Recv, Inbox, Outbox, C.MonadConc (..),- fork, forkMany, newMVar, newEmptyMVar, takeMVar, putMVar, peekMVar,- spawn, receive, statefully, (!), (<!),+ C.MVar, CC.MBox, CC.Send, CC.Recv, CC.Inbox, CC.Outbox, C.MonadConc (..),+ forkMany, newMVar, newEmptyMVar, takeMVar, putMVar, peekMVar, readMVar,+ CC.spawn, CC.receive, CC.statefully, (CC.!), (CC.<!), forever ) where import qualified Haste.Concurrent.Monad as C+import qualified Haste.Concurrent as CC import Haste.App.Client import Control.Monad instance C.MonadConc Client where liftConc = liftCIO---- | Spawn off a concurrent computation.-fork :: Client () -> Client ()-fork m = do- cs <- get id- liftCIO . C.forkIO $ runClientCIO cs m+ fork m = do+ cs <- get id+ liftCIO . C.forkIO $ runClientCIO cs m -- | Spawn several concurrent computations. forkMany :: [Client ()] -> Client ()-forkMany = mapM_ fork+forkMany = mapM_ C.fork -- | Create a new MVar with the specified contents. newMVar :: a -> Client (C.MVar a)@@ -51,57 +49,8 @@ peekMVar :: C.MVar a -> Client (Maybe a) peekMVar = liftCIO . C.peekMVar --- | An MBox is a read/write-only MVar, depending on its first type parameter.--- Used to communicate with server processes.-newtype MBox t a = MBox (C.MVar a)--data Recv-data Send--type Inbox = MBox Recv-type Outbox = MBox Send---- | Block until a message arrives in a mailbox, then return it.-receive :: Inbox a -> Client a-receive (MBox mv) = takeMVar mv---- | Creates a generic process and returns a MBox which may be used to pass--- messages to it.--- While it is possible for a process created using spawn to transmit its--- inbox to someone else, this is a very bad idea; don't do it.-spawn :: (Inbox a -> Client ()) -> Client (Outbox a)-spawn f = do- p <- newEmptyMVar- fork $ f (MBox p)- return (MBox p)---- | Creates a generic stateful process. This process is a function taking a--- state and an event argument, returning an updated state or Nothing.--- @statefully@ creates a @MBox@ that is used to pass events to the process.--- Whenever a value is written to this MBox, that value is passed to the--- process function together with the function's current state.--- If the process function returns Nothing, the process terminates.--- If it returns a new state, the process again blocks on the event MBox,--- and will use the new state to any future calls to the server function.-statefully :: st -> (st -> evt -> Client (Maybe st)) -> Client (Outbox evt)-statefully initialState handler = do- spawn $ loop initialState- where- loop st p = do- mresult <- receive p >>= handler st- case mresult of- Just st' -> loop st' p- _ -> return ()---- | Write a value to a MBox. Named after the Erlang message sending operator,--- as both are intended for passing messages to processes.--- This operation does not block until the message is delivered, but returns--- immediately.-(!) :: Outbox a -> a -> Client ()-MBox m ! x = fork $ putMVar m x---- | Perform a Client computation, then write its return value to the given--- pipe. Mnemonic: the operator is a combination of <- and !.--- Just like @(!)@, this operation is non-blocking.-(<!) :: Outbox a -> Client a -> Client ()-p <! m = do x <- m ; p ! x+-- | Read an MVar then put it back. As Javascript is single threaded, this+-- function is atomic. If this ever changes, this function will only be+-- atomic as long as no other thread attempts to write to the MVar.+readMVar :: C.MVar a -> Client a+readMVar = liftCIO . C.readMVar
libraries/haste-lib/src/Haste/App/Monad.hs view
@@ -1,11 +1,11 @@ {-# LANGUAGE CPP, GeneralizedNewtypeDeriving #-} -- | Haste.App startup monad and configuration. module Haste.App.Monad (- Exportable,- App, Server, Sessions, SessionID, Useless (..), Export (..), Done (..),+ Remotable,+ App, Server, Sessions, SessionID, Remote (..), Done (..), AppCfg, def, mkConfig, cfgURL, cfgPort,- liftServerIO, forkServerIO, export, getAppConfig,- mkUseful, runApp, (<.>), getSessionID, getActiveSessions, onSessionEnd+ liftServerIO, forkServerIO, remote, getAppConfig,+ runApp, (<.>), getSessionID, getActiveSessions, onSessionEnd ) where import Control.Applicative import Control.Monad (ap)@@ -30,6 +30,7 @@ import Control.Exception import System.Random import Data.List (foldl')+import Data.String #endif data AppCfg = AppCfg {@@ -53,15 +54,22 @@ type Sessions = S.Set SessionID type Method = [Blob] -> SessionID -> IORef Sessions -> IO Blob type Exports = M.Map CallID Method-data Useless a = Useful a | Useless newtype Done = Done (IO ()) -data Export a = Export CallID [Blob]+#ifdef __HASTE__+data Remote a = Remote CallID [Blob]+#else+data Remote a = Remote+#endif -- | Apply an exported function to an argument. -- TODO: look into making this Applicative.-(<.>) :: Binary a => Export (a -> b) -> a -> Export b-(Export cid args) <.> arg = Export cid (encode arg:args)+(<.>) :: Binary a => Remote (a -> b) -> a -> Remote b+#ifdef __HASTE__+(Remote cid args) <.> arg = Remote cid (encode arg:args)+#else+_ <.> _ = Remote+#endif -- | Application monad; allows for exporting functions, limited liftIO, -- forkIO and launching the client.@@ -89,15 +97,15 @@ -- | Lift an IO action into the Server monad, the result of which can only be -- used server-side.-liftServerIO :: IO a -> App (Useless a)+liftServerIO :: IO a -> App (Server a) #ifdef __HASTE__ {-# RULES "throw away liftServerIO"- forall x. liftServerIO x = return Useless #-}-liftServerIO _ = return Useless+ forall x. liftServerIO x = return Server #-}+liftServerIO _ = return Server #else liftServerIO m = App $ \cfg _ cid exports -> do x <- m- return (Useful x, cid, exports, cfg)+ return (return x, cid, exports, cfg) #endif -- | Fork off a Server computation not bound an API call.@@ -107,30 +115,30 @@ -- Calling @getSessionID@ inside this computation will return 0, which will -- never be generated for an actual session. @getActiveSessions@ works as -- expected.-forkServerIO :: Server () -> App (Useless ThreadId)+forkServerIO :: Server () -> App (Server ThreadId) #ifdef __HASTE__ {-# RULES "throw away forkServerIO"- forall x. forkServerIO x = return Useless #-}-forkServerIO _ = return Useless+ forall x. forkServerIO x = return Server #-}+forkServerIO _ = return Server #else forkServerIO (Server m) = App $ \cfg sessions cid exports -> do tid <- forkIO $ m 0 sessions- return (Useful tid, cid, exports, cfg)+ return (return tid, cid, exports, cfg) #endif -- | An exportable function is of the type -- (Serialize a, ..., Serialize result) => a -> ... -> IO result-class Exportable a where+class Remotable a where serializify :: a -> [Blob] -> (SessionID -> IORef Sessions -> IO Blob) -instance Binary a => Exportable (Server a) where+instance Binary a => Remotable (Server a) where #ifdef __HASTE__ serializify _ _ = undefined #else serializify (Server m) _ = \sid ss -> fmap encode (m sid ss) #endif -instance (Binary a, Exportable b) => Exportable (a -> b) where+instance (Binary a, Remotable b) => Remotable (a -> b) where #ifdef __HASTE__ serializify _ _ = undefined #else@@ -143,16 +151,16 @@ #endif -- | Make a function available to the client as an API call.-export :: Exportable a => a -> App (Export a)+remote :: Remotable a => a -> App (Remote a) #ifdef __HASTE__-{-# RULES "throw away export's argument"- forall x. export x =- App $ \c _ cid _ -> return (Export cid [], cid+1, undefined, c) #-}-export _ = App $ \c _ cid _ ->- return (Export cid [], cid+1, undefined, c)+{-# RULES "throw away remote's argument"+ forall x. remote x =+ App $ \c _ cid _ -> return (Remote cid [], cid+1, undefined, c) #-}+remote _ = App $ \c _ cid _ ->+ return (Remote cid [], cid+1, undefined, c) #else-export s = App $ \c _ cid exports ->- return (Export cid [], cid+1, M.insert cid (serializify s) exports, c)+remote s = App $ \c _ cid exports ->+ return (Remote, cid+1, M.insert cid (serializify s) exports, c) #endif -- | Register a handler to be run whenever a session terminates.@@ -230,45 +238,64 @@ go #endif --- | Server monad for Haste.App. Allows redeeming Useless values, lifting IO+-- | Server monad for Haste.App. Allows redeeming remote values, lifting IO -- actions, and not much more.+#ifdef __HASTE__+data Server a = Server+#else newtype Server a = Server {unS :: SessionID -> IORef Sessions -> IO a}+#endif instance Functor Server where+#ifdef __HASTE__+ fmap _ _ = Server+#else fmap f (Server m) = Server $ \sid ss -> f <$> m sid ss+#endif instance Applicative Server where (<*>) = ap pure = return instance Monad Server where+#ifdef __HASTE__+ return _ = Server+ _ >>= _ = Server+#else return x = Server $ \_ _ -> return x (Server m) >>= f = Server $ \sid ss -> do Server m' <- f <$> m sid ss m' sid ss+#endif instance MonadIO Server where+#ifdef __HASTE__+ liftIO _ = Server+#else liftIO m = Server $ \_ _ -> m+#endif instance MonadBlob Server where #ifndef __HASTE__ getBlobData (Blob bd) = return $ BlobData bd- getBlobText (Blob bd) = return $ BU.toString $ BS.concat $ BSL.toChunks bd+ getBlobText' (Blob bd) = return $ fromString $ BU.toString $ BS.concat $ BSL.toChunks bd #else- getBlobData _ = return undefined- getBlobText _ = return undefined+ getBlobData _ = Server+ getBlobText' _ = Server #endif --- | Make a Useless value useful by extracting it. Only possible server-side,--- in the IO monad.-mkUseful :: Useless a -> Server a-mkUseful (Useful x) = return x-mkUseful _ = error "Useless values are only useful server-side!"- -- | Returns the ID of the current session. getSessionID :: Server SessionID+#ifdef __HASTE__+getSessionID = Server+#else getSessionID = Server $ \sid _ -> return sid+#endif -- | Return all currently active sessions. getActiveSessions :: Server Sessions+#ifdef __HASTE__+getActiveSessions = Server+#else getActiveSessions = Server $ \_ ss -> readIORef ss+#endif
libraries/haste-lib/src/Haste/Binary.hs view
@@ -5,7 +5,7 @@ module Haste.Binary ( module Haste.Binary.Put, module Haste.Binary.Get,- MonadBlob (..), Binary (..),+ MonadBlob (..), Binary (..), getBlobText, Blob, BlobData, blobSize, blobDataSize, toByteString, toBlob, strToBlob, encode, decode@@ -24,9 +24,13 @@ class Monad m => MonadBlob m where -- | Retrieve the raw data from a blob. getBlobData :: Blob -> m BlobData- -- | Interpret a blob as UTF-8 text.- getBlobText :: Blob -> m String+ -- | Interpret a blob as UTF-8 text, as a JSString.+ getBlobText' :: Blob -> m JSString +-- | Interpret a blob as UTF-8 text.+getBlobText :: MonadBlob m => Blob -> m String+getBlobText b = getBlobText' b >>= return . fromJSStr+ instance MonadBlob CIO where getBlobData b = do res <- newEmptyMVar@@ -44,10 +48,10 @@ convertBlob = ffi "(function(b,cb){var r=new FileReader();r.onload=function(){A(cb,[new DataView(r.result),0]);};r.readAsArrayBuffer(b);})" - getBlobText b = do+ getBlobText' b = do res <- newEmptyMVar liftIO $ convertBlob b (toOpaque $ concurrent . putMVar res)- fromJSStr <$> takeMVar res+ takeMVar res where convertBlob :: Blob -> Opaque (JSString -> IO ()) -> IO () convertBlob = ffi
libraries/haste-lib/src/Haste/Binary/Types.hs view
@@ -13,7 +13,7 @@ #ifdef __HASTE__ data BlobData = BlobData Int Int Unpacked-newtype Blob = Blob Unpacked deriving Marshal+newtype Blob = Blob Unpacked deriving (Pack, Unpack) -- | The size, in bytes, of the contents of the given blob. blobSize :: Blob -> Int@@ -46,15 +46,21 @@ ffi "(function(b,off,len){return b.slice(off,len);})" b off len newBlob :: Unpacked -> Blob-newBlob = unsafePerformIO . ffi "(function(b){return new Blob([b]);})"+newBlob = unsafePerformIO . jsNewBlob++jsNewBlob :: Unpacked -> IO Blob+jsNewBlob =+ ffi "(function(b){try {return new Blob([b]);} catch (e) {return new Blob([b.buffer]);}})" #else newtype BlobData = BlobData BS.ByteString newtype Blob = Blob BS.ByteString -- Never used except for type checking-instance Marshal BlobData-instance Marshal Blob+instance Pack BlobData+instance Unpack BlobData+instance Pack Blob+instance Unpack Blob -- | The size, in bytes, of the contents of the given blob. blobSize :: Blob -> Int
libraries/haste-lib/src/Haste/Concurrent.hs view
@@ -1,8 +1,14 @@-{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, TypeFamilies #-}+{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, TypeFamilies,+ EmptyDataDecls #-} -- | Concurrency for Haste. Includes MVars, forking, Ajax and more.-module Haste.Concurrent (module Monad, module Ajax, wait, server) where+module Haste.Concurrent (+ module Monad, module Ajax,+ Recv, Send, Inbox, Outbox, MBox,+ receive, spawn, statefully, (!), (<!),+ wait+ ) where import Haste.Concurrent.Monad as Monad-import Haste.Concurrent.Ajax as Ajax+import Haste.Concurrent.Ajax as Ajax hiding ((!)) import Haste.Callback -- | Wait for n milliseconds.@@ -12,27 +18,62 @@ liftIO $ setTimeout' ms $ putMVar v () takeMVar v --- | Creates a generic server thread. A server is a function taking a state--- and an event argument, returning an updated state or Nothing.--- @server@ creates an MVar that is used to pass events to the server.--- Whenever a value is written to this MVar, that value is passed to the--- server function togeter with its current state.--- If the server function returns Nothing, the server thread terminates.--- If it returns a new state, the server again blocks on the event MVar,+instance GenericCallback (CIO ()) CIO where+ type CB (CIO ()) = IO ()+ mkcb toIO m = toIO m+ mkIOfier _ = return concurrent++-- | An MBox is a read/write-only MVar, depending on its first type parameter.+-- Used to communicate with server processes.+newtype MBox t a = MBox (MVar a)++data Recv+data Send++type Inbox = MBox Recv+type Outbox = MBox Send++-- | Block until a message arrives in a mailbox, then return it.+receive :: MonadConc m => Inbox a -> m a+receive (MBox mv) = liftConc $ takeMVar mv++-- | Creates a generic process and returns a MBox which may be used to pass+-- messages to it.+-- While it is possible for a process created using spawn to transmit its+-- inbox to someone else, this is a very bad idea; don't do it.+spawn :: MonadConc m => (Inbox a -> m ()) -> m (Outbox a)+spawn f = do+ p <- liftConc newEmptyMVar+ fork $ f (MBox p)+ return (MBox p)++-- | Creates a generic stateful process. This process is a function taking a+-- state and an event argument, returning an updated state or Nothing.+-- @statefully@ creates a @MBox@ that is used to pass events to the process.+-- Whenever a value is written to this MBox, that value is passed to the+-- process function together with the function's current state.+-- If the process function returns Nothing, the process terminates.+-- If it returns a new state, the process again blocks on the event MBox, -- and will use the new state to any future calls to the server function.-server :: state -> (state -> evt -> CIO (Maybe state)) -> CIO (MVar evt)-server initialState handler = do- evtvar <- newEmptyMVar- forkIO $ loop evtvar initialState- return evtvar+statefully :: MonadConc m => st -> (st -> evt -> m (Maybe st)) -> m (Outbox evt)+statefully initialState handler = do+ spawn $ loop initialState where- loop m st = do- mresult <- takeMVar m >>= handler st+ loop st p = do+ mresult <- liftConc (receive p) >>= handler st case mresult of- Just st' -> loop m st'+ Just st' -> loop st' p _ -> return () -instance GenericCallback (CIO ()) CIO where- type CB (CIO ()) = IO ()- mkcb toIO m = toIO m- mkIOfier _ = return concurrent+-- | Write a value to a MBox. Named after the Erlang message sending operator,+-- as both are intended for passing messages to processes.+-- This operation does not block until the message is delivered, but returns+-- immediately.+(!) :: MonadConc m => Outbox a -> a -> m ()+MBox m ! x = liftConc $ forkIO $ putMVar m x++-- | Perform a Client computation, then write its return value to the given+-- pipe. Mnemonic: the operator is a combination of <- and !.+-- Just like @(!)@, this operation is non-blocking.+(<!) :: MonadConc m => Outbox a -> m a -> m ()+p <! m = do x <- m ; p ! x
libraries/haste-lib/src/Haste/Concurrent/Monad.hs view
@@ -3,7 +3,7 @@ module Haste.Concurrent.Monad ( MVar, CIO, ToConcurrent (..), MonadConc (..), forkIO, forkMany, newMVar, newEmptyMVar, takeMVar, putMVar, withMVarIO,- peekMVar, modifyMVarIO, concurrent, liftIO+ peekMVar, modifyMVarIO, readMVar, concurrent, liftIO ) where import Control.Monad.IO.Class import Control.Monad.Cont.Class@@ -12,11 +12,13 @@ import Data.IORef -- | Any monad which supports concurrency.-class MonadConc m where+class Monad m => MonadConc m where liftConc :: CIO a -> m a+ fork :: m () -> m () instance MonadConc CIO where liftConc = id+ fork = forkIO -- | Embed concurrent computations into non-concurrent ones. class ToConcurrent a where@@ -108,6 +110,15 @@ case v of Full x _ -> return (Just x) _ -> return Nothing++-- | Read an MVar then put it back. As Javascript is single threaded, this+-- function is atomic. If this ever changes, this function will only be+-- atomic as long as no other thread attempts to write to the MVar.+readMVar :: MVar a -> CIO a+readMVar m = do+ x <- takeMVar m+ putMVar m x+ return x -- | Write an MVar. Blocks if the MVar is already full. -- Only the first reader in the read queue, if any, is woken.
libraries/haste-lib/src/Haste/DOM.hs view
@@ -7,7 +7,8 @@ addChildBefore, removeChild, clearChildren , getChildBefore, getLastChild, getChildren, setChildren , getStyle, setStyle, getStyle', setStyle',- getFileData, getFileName+ getFileData, getFileName,+ setClass, toggleClass, hasClass ) where import Haste.Prim import Haste.JSType@@ -17,7 +18,8 @@ import Haste.Binary.Types newtype Elem = Elem JSAny-instance Marshal Elem+instance Pack Elem+instance Unpack Elem type PropID = String type ElemID = String@@ -184,8 +186,10 @@ then Just `fmap` getFile e ix else return Nothing where+ {-# NOINLINE getFiles #-} getFiles :: Elem -> IO Int getFiles = ffi "(function(e){return e.files.length;})"+ {-# NOINLINE getFile #-} getFile :: Elem -> Int -> IO Blob getFile = ffi "(function(e,ix){return e.files[ix];})" @@ -200,3 +204,27 @@ separator '/' = True separator '\\' = True separator _ = False++-- | Add or remove a class from an element's class list.+setClass :: MonadIO m => Elem -> String -> Bool -> m ()+setClass e c x = liftIO $ setc e c x+ where+ {-# NOINLINE setc #-}+ setc :: Elem -> String -> Bool -> IO ()+ setc = ffi "(function(e,c,x){x?e.classList.add(c):e.classList.remove(c);})"++-- | Toggle the existence of a class within an elements class list.+toggleClass :: MonadIO m => Elem -> String -> m ()+toggleClass e c = liftIO $ toggc e c+ where+ {-# NOINLINE toggc #-}+ toggc :: Elem -> String -> IO ()+ toggc = ffi "(function(e,c) {e.classList.toggle(c);})"++-- | Does the given element have a particular class?+hasClass :: MonadIO m => Elem -> String -> m Bool+hasClass e c = liftIO $ getc e c+ where+ {-# NOINLINE getc #-}+ getc :: Elem -> String -> IO Bool+ getc = ffi "(function(e,c) {return e.classList.contains(c);})"
libraries/haste-lib/src/Haste/Foreign.hs view
@@ -1,10 +1,12 @@ {-# LANGUAGE ForeignFunctionInterface, EmptyDataDecls, TypeSynonymInstances, FlexibleInstances, TypeFamilies, OverlappingInstances, CPP,- OverloadedStrings #-}+ OverloadedStrings, UndecidableInstances #-} -- | Create functions on the fly from JS strings. -- Slower but more flexible alternative to the standard FFI. module Haste.Foreign (- FFI, Marshal (..), Unpacked, Opaque, ffi, export, toOpaque, fromOpaque+ FFI, Pack (..), Unpack (..), Marshal,+ Unpacked, Opaque,+ ffi, export, toOpaque, fromOpaque ) where import Haste.Prim import Haste.JSType@@ -43,59 +45,188 @@ data Dummy = Dummy Unpacked --- | Class for marshallable types. Pack takes an opaque JS value and turns it--- into the type's proper Haste representation, and unpack is its inverse.--- The default instances make an effort to prevent wrongly typed values--- through, but you could probably break them with enough creativity.-class Marshal a where+class Pack a where pack :: Unpacked -> a pack = unsafePack +class Unpack a where unpack :: a -> Unpacked unpack = unsafeUnpack -instance Marshal Float-instance Marshal Double-instance Marshal JSString where+-- | Class for marshallable types. Pack takes an opaque JS value and turns it+-- into the type's proper Haste representation, and unpack is its inverse.+-- The default instances make an effort to prevent wrongly typed values+-- through, but you could probably break them with enough creativity.+class (Pack a, Unpack a) => Marshal a+instance (Pack a, Unpack a) => Marshal a++instance Pack Float+instance Pack Double+instance Pack JSAny+instance Pack JSString where pack = jsString . unsafePack-instance Marshal Int where+instance Pack Int where pack x = convert (unsafePack x :: Double)-instance Marshal Int8 where+instance Pack Int8 where pack x = convert (unsafePack x :: Double)-instance Marshal Int16 where+instance Pack Int16 where pack x = convert (unsafePack x :: Double)-instance Marshal Int32 where+instance Pack Int32 where pack x = convert (unsafePack x :: Double)-instance Marshal Word where+instance Pack Word where pack x = convert (unsafePack x :: Double)-instance Marshal Word8 where+instance Pack Word8 where pack x = convert (unsafePack x :: Double)-instance Marshal Word16 where+instance Pack Word16 where pack x = convert (unsafePack x :: Double)-instance Marshal Word32 where+instance Pack Word32 where pack x = convert (unsafePack x :: Double)-instance Marshal () where+instance Pack () where pack _ = ()- unpack _ = unpack (0 :: Double)-instance Marshal String where+instance Pack String where pack = fromJSStr . pack- unpack = unpack . toJSStr-instance Marshal Unpacked where+instance Pack Unpacked where pack = id- unpack = id-instance Marshal (Opaque a) where+instance Pack (Opaque a) where pack = Opaque+instance Pack Bool where+ pack x = if pack x > (0 :: Double) then True else False++-- | Lists are marshalled into arrays.+instance Pack a => Pack [a] where+ pack arr = map pack . fromOpaque $ arr2lst arr 0++-- | Maybe is simply a nullable type. Nothing is equivalent to null, and any+-- non-null value is equivalent to x in Just x.+instance Pack a => Pack (Maybe a) where+ pack x = if isNull x then Nothing else Just (pack x)++-- | Tuples are marshalled into arrays.+instance (Pack a, Pack b) => Pack (a, b) where+ pack x = case pack x of [a, b] -> (pack a, pack b)++instance (Pack a, Pack b, Pack c) => Pack (a, b, c) where+ pack x = case pack x of [a, b, c] -> (pack a, pack b, pack c)++instance (Pack a, Pack b, Pack c, Pack d) =>+ Pack (a, b, c, d) where+ pack x = case pack x of [a, b, c, d] -> (pack a, pack b, pack c, pack d)++instance (Pack a, Pack b, Pack c, Pack d, Pack e) =>+ Pack (a, b, c, d, e) where+ pack x = case pack x of [a,b,c,d,e] -> (pack a, pack b, pack c, pack d, pack e)++instance (Pack a, Pack b, Pack c, Pack d, Pack e,+ Pack f) => Pack (a, b, c, d, e, f) where+ pack x = case pack x of+ [a, b, c, d, e, f] -> (pack a, pack b, pack c, pack d, pack e, pack f)++instance (Pack a, Pack b, Pack c, Pack d, Pack e,+ Pack f, Pack g) => Pack (a, b, c, d, e, f, g) where+ pack x = case pack x of+ [a, b, c, d, e, f, g] -> (pack a,pack b,pack c,pack d,pack e,pack f,pack g)++instance (Pack a, Pack b, Pack c, Pack d, Pack e,+ Pack f, Pack g, Pack h) =>+ Pack (a, b, c, d, e, f, g, h) where+ pack x = case pack x of+ [a, b, c, d, e, f, g, h] -> (pack a, pack b, pack c, pack d, pack e,+ pack f, pack g, pack h)++instance (Pack a, Pack b, Pack c, Pack d, Pack e,+ Pack f, Pack g, Pack h, Pack i) =>+ Pack (a, b, c, d, e, f, g, h, i) where+ pack x = case pack x of+ [a, b, c, d, e, f, g, h, i] -> (pack a, pack b, pack c, pack d, pack e,+ pack f, pack g, pack h, pack i)++instance (Pack a, Pack b, Pack c, Pack d, Pack e,+ Pack f, Pack g, Pack h, Pack i, Pack j) =>+ Pack (a, b, c, d, e, f, g, h, i, j) where+ pack x = case pack x of+ [a, b, c, d, e, f, g, h, i, j] -> (pack a, pack b, pack c, pack d, pack e,+ pack f, pack g, pack h, pack i, pack j)++instance Unpack Float+instance Unpack Double+instance Unpack JSAny+instance Unpack JSString+instance Unpack Int+instance Unpack Int8+instance Unpack Int16+instance Unpack Int32+instance Unpack Word+instance Unpack Word8+instance Unpack Word16+instance Unpack Word32+instance Unpack () where+ unpack _ = unpack (0 :: Double)+instance Unpack String where+ unpack = unpack . toJSStr+instance Unpack Unpacked where+ unpack = id+instance Unpack (Opaque a) where unpack (Opaque x) = x-instance Marshal Bool where+instance Unpack Bool where unpack True = jsTrue unpack False = jsFalse- pack x = if pack x > (0 :: Double) then True else False -- | Lists are marshalled into arrays.-instance Marshal a => Marshal [a] where+instance Unpack a => Unpack [a] where unpack = lst2arr . toOpaque . map unpack- pack arr = map pack . fromOpaque $ arr2lst arr 0 +-- | Maybe is simply a nullable type. Nothing is equivalent to null, and any+-- non-null value is equivalent to x in Just x.+instance Unpack a => Unpack (Maybe a) where+ unpack Nothing = jsNull+ unpack (Just x) = unpack x++-- | Tuples are marshalled into arrays.+instance (Unpack a, Unpack b) => Unpack (a, b) where+ unpack (a, b) = unpack [unpack a, unpack b]++instance (Unpack a, Unpack b, Unpack c) => Unpack (a, b, c) where+ unpack (a, b, c) = unpack [unpack a, unpack b, unpack c]++instance (Unpack a, Unpack b, Unpack c, Unpack d) =>+ Unpack (a, b, c, d) where+ unpack (a, b, c, d) = unpack [unpack a, unpack b, unpack c, unpack d]++instance (Unpack a, Unpack b, Unpack c, Unpack d, Unpack e) =>+ Unpack (a, b, c, d, e) where+ unpack (a, b, c, d, e) = unpack [unpack a,unpack b,unpack c,unpack d,unpack e]++instance (Unpack a, Unpack b, Unpack c, Unpack d, Unpack e,+ Unpack f) => Unpack (a, b, c, d, e, f) where+ unpack (a, b, c, d, e, f) =+ unpack [unpack a, unpack b, unpack c, unpack d, unpack e, unpack f]++instance (Unpack a, Unpack b, Unpack c, Unpack d, Unpack e,+ Unpack f, Unpack g) => Unpack (a, b, c, d, e, f, g) where+ unpack (a, b, c, d, e, f, g) =+ unpack [unpack a,unpack b,unpack c,unpack d,unpack e,unpack f,unpack g]++instance (Unpack a, Unpack b, Unpack c, Unpack d, Unpack e,+ Unpack f, Unpack g, Unpack h) =>+ Unpack (a, b, c, d, e, f, g, h) where+ unpack (a, b, c, d, e, f, g, h) =+ unpack [unpack a, unpack b, unpack c, unpack d, unpack e,+ unpack f, unpack g, unpack h]++instance (Unpack a, Unpack b, Unpack c, Unpack d, Unpack e,+ Unpack f, Unpack g, Unpack h, Unpack i) =>+ Unpack (a, b, c, d, e, f, g, h, i) where+ unpack (a, b, c, d, e, f, g, h, i) =+ unpack [unpack a, unpack b, unpack c, unpack d, unpack e,+ unpack f, unpack g, unpack h, unpack i]++instance (Unpack a, Unpack b, Unpack c, Unpack d, Unpack e,+ Unpack f, Unpack g, Unpack h, Unpack i, Unpack j) =>+ Unpack (a, b, c, d, e, f, g, h, i, j) where+ unpack (a, b, c, d, e, f, g, h, i, j) =+ unpack [unpack a, unpack b, unpack c, unpack d, unpack e,+ unpack f, unpack g, unpack h, unpack i, unpack j]+ {-# RULES "unpack array/Unpacked" forall x. unpack x = lst2arr (toOpaque x) #-} {-# RULES "pack array/Unpacked" forall x. pack x = fromOpaque (arr2lst x 0) #-} @@ -103,14 +234,7 @@ lst2arr = unsafePerformIO . ffi "lst2arr" arr2lst :: Unpacked -> Int -> Opaque [Unpacked]-arr2lst arr ix = unsafePerformIO $ ffi "lst2arr" arr ix---- | Maybe is simply a nullable type. Nothing is equivalent to null, and any--- non-null value is equivalent to x in Just x.-instance Marshal a => Marshal (Maybe a) where- unpack Nothing = jsNull- unpack (Just x) = unpack x- pack x = if isNull x then Nothing else Just (pack x)+arr2lst arr ix = unsafePerformIO $ ffi "arr2lst" arr ix jsNull, jsTrue, jsFalse :: Unpacked jsTrue = unsafePerformIO $ ffi "true"
libraries/haste-lib/src/Haste/Graphics/Canvas.hs view
@@ -12,7 +12,7 @@ -- Working with bitmaps bitmapElem, -- Rendering pictures, extracting data from a canvas- render, buffer, toDataURL,+ render, renderOnTop, buffer, toDataURL, -- Working with colors and opacity setStrokeColor, setFillColor, color, opacity, -- Matrix operations@@ -210,6 +210,12 @@ render (Canvas ctx el) (Picture p) = liftIO $ do jsResetCanvas el p ctx++-- | Draw a picture onto a canvas without first clearing it.+{-# SPECIALISE renderOnTop :: Canvas -> Picture a -> IO a #-}+{-# SPECIALISE renderOnTop :: Canvas -> Picture a -> CIO a #-}+renderOnTop :: MonadIO m => Canvas -> Picture a -> m a+renderOnTop (Canvas ctx el) (Picture p) = liftIO $ p ctx -- | Generate a data URL from the contents of a canvas. toDataURL :: MonadIO m => Canvas -> m URL
libraries/haste-lib/src/Haste/Hash.hs view
@@ -1,6 +1,8 @@ {-# LANGUAGE FlexibleInstances, FlexibleContexts, GADTs, OverloadedStrings #-} -- | Hash manipulation and callbacks.-module Haste.Hash (onHashChange, setHash, getHash) where+module Haste.Hash (+ onHashChange, onHashChange', setHash, getHash, setHash', getHash'+ ) where import Haste.Foreign import Control.Monad.IO.Class import Haste.Callback@@ -9,8 +11,9 @@ newtype HashCallback = HashCallback (JSString -> JSString -> IO ()) -instance Marshal HashCallback where+instance Pack HashCallback where pack = unsafeCoerce+instance Unpack HashCallback where unpack = unsafeCoerce -- | Register a callback to be run whenever the URL hash changes.@@ -19,21 +22,52 @@ => (String -> String -> m ()) -> m () onHashChange f = do+ firsthash <- getHash' f' <- toCallback $ \old new -> f (fromJSStr old) (fromJSStr new)- liftIO $ go (HashCallback f')- where- go :: HashCallback -> IO ()- go = ffi "(function(cb) {\- \ window.onhashchange = function(e){\- \ A(cb, [[0,e.oldURL.split('#')[1] || ''],\- \ [0,e.newURL.split('#')[1] || ''],0]);\- \ };\- \})"+ liftIO $ jsOnHashChange firsthash (HashCallback f') +-- | JSString version of @onHashChange@.+onHashChange' :: (MonadIO m, GenericCallback (m ()) m, CB (m ()) ~ IO ())+ => (JSString -> JSString -> m ())+ -> m ()+onHashChange' f = do+ firsthash <- getHash'+ f' <- toCallback f+ liftIO $ jsOnHashChange firsthash (HashCallback f')++{-# NOINLINE jsOnHashChange #-}+jsOnHashChange :: JSString -> HashCallback -> IO ()+jsOnHashChange =+ ffi "(function(firsthash,cb){\+ \window.__old_hash = firsthash;\+ \window.onhashchange = function(e){\+ \var oldhash = window.__old_hash;\+ \var newhash = window.location.hash.split('#')[1] || '';\+ \window.__old_hash = newhash;\+ \A(cb, [[0,oldhash],[0,newhash],0]);\+ \};\+ \})"+ -- | Set the hash part of the current URL. setHash :: MonadIO m => String -> m ()-setHash = liftIO . ffi "(function(h) {location.hash = '#'+h;})"+setHash = liftIO . jsSetHash . toJSStr +-- | Set the hash part of the current URL - JSString version.+setHash' :: MonadIO m => JSString -> m ()+setHash' = liftIO . jsSetHash++{-# NOINLINE jsSetHash #-}+jsSetHash :: JSString -> IO ()+jsSetHash = ffi "(function(h) {location.hash = '#'+h;})"+ -- | Read the hash part of the currunt URL. getHash :: MonadIO m => m String-getHash = liftIO $ ffi "(function() {return location.hash.substring(1);})"+getHash = liftIO $ fromJSStr `fmap` jsGetHash++-- | Read the hash part of the currunt URL - JSString version.+getHash' :: MonadIO m => m JSString+getHash' = liftIO jsGetHash++{-# NOINLINE jsGetHash #-}+jsGetHash :: IO JSString+jsGetHash = ffi "(function() {return location.hash.substring(1);})"
libraries/haste-lib/src/Haste/JSON.hs view
@@ -5,7 +5,7 @@ -- the parser is implemented entirely in Javascript, and works with any -- browser that supports JSON.parse; IE does this from version 8 and up, and -- everyone else has done it since just about forever.-module Haste.JSON (JSON (..), encodeJSON, decodeJSON, (!), (~>)) where+module Haste.JSON (JSON (..), encodeJSON, decodeJSON, toObject, (!), (~>)) where import Haste.Prim import Data.String as S #ifndef __HASTE__@@ -14,6 +14,17 @@ import Data.Char (ord) import Numeric (showHex) #endif++-- | Create a Javascript object from a JSON object. Only makes sense in a+-- browser context, obviously.+toObject :: JSON -> JSAny+#ifdef __HASTE__+toObject = jsJSONParse . encodeJSON+foreign import ccall jsJSONParse :: JSString -> JSAny+#else+toObject j = error $ "Call to toObject in non-browser: " ++ show j+#endif+ -- Remember to update jsParseJSON if this data type changes! data JSON
libraries/haste-lib/src/Haste/Random.hs view
@@ -1,46 +1,63 @@-{-# LANGUAGE ForeignFunctionInterface, CPP #-}+{-# LANGUAGE GeneralizedNewtypeDeriving, CPP, OverloadedStrings #-} module Haste.Random (Random (..), Seed, next, mkSeed, newSeed) where import Haste.JSType import Data.Int import Data.Word import Data.List (unfoldr) import Control.Monad.IO.Class-#ifndef __HASTE__-import System.Random (randomIO)+import System.IO.Unsafe+#ifdef __HASTE__+import Haste.Foreign+#else+import qualified System.Random as SR #endif #ifdef __HASTE__-foreign import ccall jsRand :: IO Double++newtype Seed = Seed Unpacked deriving (Pack, Unpack)++{-# NOINLINE nxt #-}+nxt :: Seed -> IO Seed+nxt = ffi "(function(s){return md51(s.join(','));})"++{-# NOINLINE getN #-}+getN :: Seed -> IO Int+getN = ffi "(function(s){return s[0];})"++{-# NOINLINE toSeed #-}+toSeed :: Int -> IO Seed+toSeed = ffi "(function(n){return md51(n.toString());})"++{-# NOINLINE createSeed #-}+createSeed :: IO Seed+createSeed = ffi "(function(){return md51(jsRand().toString());})" #else-jsRand :: IO Double-jsRand = randomIO-#endif+newtype Seed = Seed (Int, SR.StdGen) -newtype Seed = Seed Int+nxt :: Seed -> IO Seed+nxt (Seed (_, g)) = return . Seed $ SR.next g +getN :: Seed -> IO Int+getN (Seed (n, _)) = return n++toSeed :: Int -> IO Seed+toSeed = return . Seed . SR.next . SR.mkStdGen++createSeed :: IO Seed+createSeed = SR.newStdGen >>= return . Seed . SR.next+#endif+ -- | Create a new seed from an integer. mkSeed :: Int -> Seed-mkSeed = Seed . convert+mkSeed = unsafePerformIO . toSeed -- | Generate a new seed using Javascript's PRNG. newSeed :: MonadIO m => m Seed-newSeed = liftIO $ do- x <- jsRand- s <- jsRand- let sign = if s > 0.5 then 1 else -1- return . mkSeed . round $ x*sign*2147483647+newSeed = liftIO createSeed -- | Generate the next seed in the sequence. next :: Seed -> Seed-next (Seed s) =- Seed s'- where- -- This is the same LCG that's used in older glibc versions.- -- It was chosen because the untruncated product will never be larger than- -- 2^53 and thus not cause precision problems with JS.- a = 69069- c = 1- s' = a*s+c+next = unsafePerformIO . nxt class Random a where -- | Generate a pseudo random number between a lower (inclusive) and higher@@ -50,14 +67,12 @@ randomRs bounds seed = unfoldr (Just . randomR bounds) seed instance Random Int where- randomR (low, high) s@(Seed n) =- (n' `mod` (high-low) + low, next s)- where- -- Use the LCG from MSVC here; less apparent relationship between seed- -- and output.- a = 214013- c = 2531011- n' = a*n+c+ randomR (low, high) s+ | low <= high =+ let n = unsafePerformIO $ getN s+ in (n `mod` (high-low+1) + low, next s)+ | otherwise =+ randomR (high, low) s instance Random Int32 where randomR (l,h) seed =
− libraries/haste-lib/src/Haste/Reactive.hs
@@ -1,4 +0,0 @@-module Haste.Reactive (module Fursuit, module DOM, module Ajax) where-import FRP.Fursuit as Fursuit-import Haste.Reactive.DOM as DOM-import Haste.Reactive.Ajax as Ajax
− libraries/haste-lib/src/Haste/Reactive/Ajax.hs
@@ -1,34 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Haste.Reactive.Ajax (jsonSig, ajaxSig) where-import FRP.Fursuit-import FRP.Fursuit.Async-import Haste.Ajax-import Haste.JSON-import Control.Applicative-import Control.Monad.IO.Class---- | Create a signal for JSON request. When triggered, the signal causes an--- XMLHttpRequest to be made to the given URL, with a query string built from--- the given (key, value) pairs. It notifies its listeners after the request--- returns.--- If no data was received, or if the data wasn't JSON, don't notify--- listeners.-jsonSig :: MonadIO m => Signal URL -> Signal [(Key, Val)] -> m (Signal JSON)-jsonSig requrl kvs = liftIO $ async (ajax <$> requrl <*> kvs)- where- ajax url kv p = do- jsonRequest GET url kv $ \mjson ->- case mjson of- Just json -> write p json- _ -> return ()---- | Create a signal for a generic AJAX request. It works just like 'jsonSig',--- but returns its output as plain text rather than JSON.-ajaxSig :: MonadIO m => Signal URL -> Signal [(Key, Val)] -> m (Signal String)-ajaxSig requrl kvs = liftIO $ async (ajax <$> requrl <*> kvs)- where- ajax url kv p = do- textRequest GET url kv $ \mtext -> do- case mtext of- Just text -> write p text- _ -> return ()
− libraries/haste-lib/src/Haste/Reactive/DOM.hs
@@ -1,93 +0,0 @@-{-# LANGUAGE GADTs, FlexibleInstances, MultiParamTypeClasses, - FlexibleContexts, OverlappingInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--- | DOM events and utilities for the Haste reactive library.-module Haste.Reactive.DOM (clicked,valueOf,valueAt,ElemProp,elemProp) where-import FRP.Fursuit-import Haste-import qualified Data.Map as M-import System.IO.Unsafe (unsafePerformIO)-import Data.IORef--{-# NOINLINE eventHandlers #-}--- | Contains a list of all installed event handlers.-eventHandlers :: JSType a => IORef (M.Map (ElemID, Event IO e) (Signal a))-eventHandlers = unsafePerformIO $ newIORef M.empty---- | Represents a property of a DOM object.-data ElemProp where- D :: ElemID -> PropID -> ElemProp---- | Create a 'DOMObject' from a string describing the object. For example,--- domObj "myobject.value" corresponds to the value attribute of the object--- with the ID "myobject".-elemProp :: String -> ElemProp-elemProp str =- case span (/= '.') str of- ([], _) -> error "elemProp: No object ID given!"- (_, []) -> error "elemProp: No object attribute given!"- (obj, attr) -> D obj (tail attr)--unlessExists :: JSType a => ElemID -> Event IO e -> IO (Signal a) -> Signal a-unlessExists eid evt create = new $ do- handlers <- readIORef eventHandlers- case M.lookup (eid, evt) handlers of- Just s -> return s- _ -> do- sig <- create- writeIORef eventHandlers (M.insert (eid, evt) sig handlers)- return sig----- | An event that gets raised whenever the element with the specified ID is--- clicked.-clicked :: ElemID -> Signal ()-clicked eid = unlessExists eid OnClick clickedIO- where- clickedIO = withElem eid $ \e -> do- (p,s) <- pipe ()- _ <- setCallback e OnClick (\_ _ -> write p ())- return s---- | The value property of the given element, updated whenever an onchange--- event is raised.-valueOf :: JSType a => ElemID -> Signal a-valueOf e = e `valueAt` OnChange---- | The value property of the given element, triggered on a custom event.-valueAt :: (JSType a, Callback e) => ElemID -> Event IO e -> Signal a-valueAt eid evt = filterMapS fromString $ unlessExists eid evt valueAtIO- where- valueAtIO = withElem eid $ \e -> do- str <- getProp e "value"- (src, sig) <- pipe str- success <- setCallback e evt $ constCallback $ do- getProp e "value" >>= write src-- if (not success) - then error $ "Browser doesn't support sane event handlers!"- else return sig---- | Like show, but strips enclosing quotes.-toStr :: Show a => a -> String-toStr x =- case show x of- ('"':xs) -> init xs- xs -> xs--instance Show a => Sink ElemProp a where- (D obj attr) << val = withElem obj $ \e -> sink (setProp e attr . toStr) val---- | Replace the sink element's list of child nodes whenever a new list of--- nodes comes down the pipe.-instance Sink Elem [Elem] where- e << val = sink (setChildren e) val---- | Same as the instance for [Elem].-instance Sink Elem [IO Elem] where- e << val = sink (\children -> sequence children >>= setChildren e) val---- | Set the sink element's innerHTML property whenever a new string comes down--- the pipe.-instance Sink Elem String where- e << val = sink (setProp e "innerHTML") val
libraries/haste-lib/src/Haste/WebSockets.hs view
@@ -15,22 +15,28 @@ newtype WSOnBinMsg = WSOnBinMsg (WebSocket -> Blob -> IO ()) newtype WSComputation = WSComputation (WebSocket -> IO ()) newtype WSOnError = WSOnError (IO ())- data WebSocket-instance Marshal WebSocket where++instance Pack WebSocket where pack = unsafeCoerce- unpack = unsafeCoerce-instance Marshal WSOnMsg where+instance Pack WSOnMsg where pack = unsafeCoerce- unpack = unsafeCoerce-instance Marshal WSOnBinMsg where+instance Pack WSOnBinMsg where pack = unsafeCoerce- unpack = unsafeCoerce-instance Marshal WSComputation where+instance Pack WSComputation where pack = unsafeCoerce- unpack = unsafeCoerce-instance Marshal WSOnError where+instance Pack WSOnError where pack = unsafeCoerce++instance Unpack WebSocket where+ unpack = unsafeCoerce+instance Unpack WSOnMsg where+ unpack = unsafeCoerce+instance Unpack WSOnBinMsg where+ unpack = unsafeCoerce+instance Unpack WSComputation where+ unpack = unsafeCoerce+instance Unpack WSOnError where unpack = unsafeCoerce -- | Run a computation with a web socket. The computation will not be executed
src/ArgSpecs.hs view
@@ -39,6 +39,11 @@ info = "Run the Google Closure compiler on the output. " ++ "Use --opt-google-closure=foo.jar to hint that foo.jar " ++ "is the Closure compiler."},+ ArgSpec { optName = "opt-google-closure-flag=",+ updateCfg = updateClosureFlags,+ info = "Add an extra flag for the Google Closure compiler to take. "+ ++ "Use --opt-google-closure-flag='--language_in=ECMASCRIPT5_STRICT', "+ ++ "to optimize programs to strict mode"}, ArgSpec { optName = "opt-sloppy-tce", updateCfg = useSloppyTCE, info = "Allow the possibility that some tail recursion may not "@@ -67,8 +72,11 @@ info = "Perform optimizations over the whole program at link " ++ "time. May significantly increase compilation time."}, ArgSpec { optName = "out=",- updateCfg = \cfg outfile -> cfg {outFile = const $ head outfile},- info = "Write the JS blob to <arg>."},+ updateCfg = \cfg outfile -> cfg {outFile = \_ _ -> head outfile},+ info = "Write compiler output to <arg>."},+ ArgSpec { optName = "output-html",+ updateCfg = \cfg _ -> cfg {outputHTML = True},+ info = "Produce a skeleton HTML file containing the program."}, ArgSpec { optName = "separate-namespace", updateCfg = \cfg _ -> cfg {wrapProg = True}, info = "Wrap the program in its own namespace? "@@ -130,6 +138,11 @@ cfg {useGoogleClosure = Just arg} updateClosureCfg cfg _ = cfg {useGoogleClosure = Just closureCompiler}++-- | Add flags for Google Closure to use+updateClosureFlags :: Config -> [String] -> Config+updateClosureFlags cfg args = cfg {+ useGoogleClosureFlags = useGoogleClosureFlags cfg ++ args} -- | Enable optimizations over the entire program. enableWholeProgramOpts :: Config -> [String] -> Config
src/Data/JSTarget/Constructors.hs view
@@ -95,12 +95,7 @@ -- | Create a thunk. thunk :: AST Stm -> AST Exp-thunk stm@(AST s js) =- case s of- (Return ex) | not $ evaluates ex ->- AST ex js- _ ->- callForeign "new T" [Fun Nothing [] <$> stm]+thunk stm = callForeign "new T" [Fun Nothing [] <$> stm] -- | Unpack the given expression if it's a thunk without internal bindings. fromThunk :: AST Exp -> Maybe (AST Exp)
src/Data/JSTarget/Optimize.hs view
@@ -35,6 +35,7 @@ >>= optimizeThunks >>= optimizeArrays >>= zapJSStringConversions+ >>= optUnsafeEval -- | Attempt to turn two case branches into a ternary operator expression. tryTernary :: Var@@ -138,7 +139,6 @@ return keep inl _ stm = return stm - -- | Turn if(foo) {return bar;} else {return baz;} into return foo ? bar : baz. ifReturnToTernary :: JSTrav ast => ast -> TravM ast ifReturnToTernary ast = do@@ -181,7 +181,6 @@ opt x = return x - -- | Optimize thunks in the following ways: -- A(thunk(return f), xs) -- => A(f, xs)@@ -189,8 +188,11 @@ -- => x -- E(\x ... -> ...) -- => \x ... -> ...--- thunk(x) where x is non-computing+-- thunk(x) where x is non-computing and non-recursive -- => x+--+-- TODO: figure out efficient way to only perform the 4th optimization when x+-- is not recursive. optimizeThunks :: JSTrav ast => ast -> TravM ast optimizeThunks ast = mapJS (const True) optEx return ast@@ -200,8 +202,8 @@ | Fun _ _ _ <- x = return x optEx (Call arity calltype f args) | Just f' <- fromThunkEx f = return $ Call arity calltype f' args- optEx ex | Just ex' <- fromThunkEx ex, not (computingEx ex') =- return ex'+-- optEx ex | Just ex' <- fromThunkEx ex, not (computingEx ex') =+-- return ex' optEx ex = return ex @@ -251,8 +253,11 @@ case ex of (Call ar c (Var (Internal (Name "unsafeEval" (Just (pkg, "Haste.Foreign"))) _))- [Arr [Lit (LNum 0), Lit (LStr s)]]) | take 9 pkg == "haste-lib" -> do- return $ Verbatim s+ (Arr [Lit (LNum 0), Lit (LStr s)] : args))+ | take 9 pkg == "haste-lib" -> do+ case args of+ [] -> return $ Verbatim s+ _ -> return $ Call (ar-1) c (Verbatim s) args _ -> do return ex @@ -423,5 +428,6 @@ contains (Arr xs) var = any (`contains` var) xs contains (AssignEx l r) var = l `contains` var || r `contains` var contains (IfEx c t e) var = any (`contains` var) [c,t,e]+ contains (Verbatim _) _ = False tailLoopify _ fun = do return fun
src/Haste/CodeGen.hs view
@@ -9,6 +9,9 @@ import Data.Char import Data.List (partition, foldl') import Data.Maybe (isJust)+#if __GLASGOW_HASKELL__ >= 707+import qualified Data.ByteString.Char8 as B+#endif import qualified Data.Set as S import qualified Data.Map as M -- STG/GHC stuff@@ -38,7 +41,6 @@ import Haste.Errors import Haste.PrimOps import Haste.Builtins-import Haste.Util (showOutputable) generate :: Config -> Fingerprint@@ -56,7 +58,7 @@ } where theMod = genAST cfg modname binds- + insFun m (_, AST (Assign (NewVar _ (Internal v _)) body _) jumps) = M.insert v (AST body jumps) m insFun m _ =@@ -139,7 +141,7 @@ cfg <- getCfg let theOp = case op of StgPrimOp op' ->- maybeTrace cfg (showOutputable op') args' <$> genOp cfg op' args'+ maybeTrace cfg (showOutputable cfg op') args' <$> genOp cfg op' args' StgPrimCallOp (PrimCall f _) -> Right $ maybeTrace cfg fs args' $ callForeign fs args' where fs = unpackFS f@@ -194,7 +196,7 @@ -- a recursive binding; this is because it's quite a lot easier to keep track -- of which functions depend on each other if every genBind call results in a -- single function being generated.--- Use `genBindRec` to generate code for local potentially recursive bindings +-- Use `genBindRec` to generate code for local potentially recursive bindings -- as their dependencies get merged into their parent's anyway. genBind :: Bool -> Maybe Int -> StgBinding -> JSGen Config () genBind onTopLevel funsInRecGroup (StgNonRec v rhs) = do@@ -245,7 +247,7 @@ genArgVarsPair :: [(Var.Var, a)] -> JSGen Config ([J.Var], [a]) genArgVarsPair vps = do vs' <- mapM genVar vs- return (vs', xs) + return (vs', xs) where (vs, xs) = unzip $ filter (hasRepresentation . fst) vps @@ -300,7 +302,7 @@ splitAlts alts = case partition isDefault alts of ([defAlt], otherAlts) -> (defAlt, otherAlts)- ([], otherAlts) -> (last otherAlts, init otherAlts) + ([], otherAlts) -> (last otherAlts, init otherAlts) _ -> error "More than one default alt in case!" where isDefault (DEFAULT, _, _, _) = True@@ -326,7 +328,9 @@ -- | Generate a result variable for the given scrutinee variable. genResultVar :: Var.Var -> JSGen Config J.Var-genResultVar v = (\mn -> toJSVar mn v (Just "#result")) <$> getModName+genResultVar v = do+ cfg <- getCfg+ (\mn -> toJSVar cfg mn v (Just "#result")) <$> getModName -- | Generate a new variable and add a dependency on it to the function -- currently being generated.@@ -336,7 +340,8 @@ Just v' -> return v' _ -> do mymod <- getModName- v' <- return $ toJSVar mymod v Nothing+ cfg <- getCfg+ v' <- return $ toJSVar cfg mymod v Nothing dependOn v' return v' genVar _ = do@@ -354,8 +359,8 @@ foreignName _ = error "Dynamic foreign calls not supported!" -toJSVar :: String -> Var.Var -> Maybe String -> J.Var-toJSVar thisMod v msuffix =+toJSVar :: Config -> String -> Var.Var -> Maybe String -> J.Var+toJSVar c thisMod v msuffix = case idDetails v of FCallId fc -> foreignVar (foreignName fc) _@@ -377,7 +382,7 @@ myMod = maybe thisMod (moduleNameString . moduleName) (nameModule_maybe vname) myPkg =- maybe "main" (showOutputable . modulePackageId) (nameModule_maybe vname)+ maybe "main" (showOutputable c . modulePackageId) (nameModule_maybe vname) extern = occNameString $ nameOccName vname unique = show $ nameUnique vname @@ -456,7 +461,11 @@ genLit :: L.Literal -> JSGen Config (AST Exp) genLit l = do case l of+#if __GLASGOW_HASKELL__ >= 707+ MachStr s -> return . lit $ B.unpack s+#else MachStr s -> return . lit $ unpackFS s+#endif MachInt n | n > 2147483647 || n < -2147483648 -> do warn Verbose (constFail "Int" n)@@ -501,7 +510,7 @@ -- | Does this data constructor create an enumeration type? isEnumerationDataCon :: DataCon -> Bool-isEnumerationDataCon = isEnumerationTyCon . dataConTyCon +isEnumerationDataCon = isEnumerationTyCon . dataConTyCon -- | Returns True if the given Var is an unboxed tuple with a single element -- after any represenationless elements are discarded.
src/Haste/Config.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings, Rank2Types #-} module Haste.Config ( Config (..), AppStart, defConfig, stdJSLibs, startCustom, fastMultiply, safeMultiply, debugLib) where@@ -8,6 +8,7 @@ import Blaze.ByteString.Builder.Char.Utf8 import Data.Monoid import Haste.Environment+import Outputable (Outputable) type AppStart = Builder -> Builder @@ -75,7 +76,7 @@ ppOpts :: PPOpts, -- | A function that takes the name of the a target as its input and -- outputs the name of the file its JS blob should be written to.- outFile :: String -> String,+ outFile :: Config -> String -> String, -- | Link the program? performLink :: Bool, -- | A function to call on each Int arithmetic primop.@@ -93,8 +94,16 @@ tracePrimops :: Bool, -- | Run the entire thing through Google Closure when done? useGoogleClosure :: Maybe FilePath,+ -- | Extra flags for Google Closure to take?+ useGoogleClosureFlags :: [String], -- | Any external Javascript to link into the JS bundle.- jsExternals :: [FilePath]+ jsExternals :: [FilePath],+ -- | Produce a skeleton HTML file containing the program rather than a+ -- JS file.+ outputHTML :: Bool,+ -- | GHC DynFlags used for STG generation.+ -- Currently only used for printing StgSyn values.+ showOutputable :: forall a. Outputable a => a -> String } -- | Default compiler configuration.@@ -106,7 +115,10 @@ appStart = startOnLoadComplete, wrapProg = False, ppOpts = def,- outFile = flip replaceExtension "js",+ outFile = \cfg f -> let ext = if outputHTML cfg+ then "html"+ else "js"+ in replaceExtension f ext, performLink = True, wrapIntMath = strictly32Bits, multiplyIntOp = safeMultiply,@@ -115,5 +127,8 @@ sloppyTCE = False, tracePrimops = False, useGoogleClosure = Nothing,- jsExternals = []+ useGoogleClosureFlags = [],+ jsExternals = [],+ outputHTML = False,+ showOutputable = const "No showOutputable defined in config!" }
src/Haste/Linker.hs view
@@ -29,7 +29,7 @@ rtslibs <- mapM readFile $ rtsLibs cfg extlibs <- mapM readFile $ jsExternals cfg- B.writeFile (outFile cfg target)+ B.writeFile (outFile cfg cfg target) $ toLazyByteString $ assembleProg (wrapProg cfg) extlibs rtslibs progText callMain launchApp where
src/Haste/PrimOps.hs view
@@ -4,7 +4,6 @@ import PrimOp import Data.JSTarget import Haste.Config-import Haste.Util -- | Dummy State# RealWorld value for where one is needed. defState :: AST Exp@@ -315,7 +314,7 @@ -- noDuplicate is only relevant in a threaded environment. NoDuplicateOp -> Right $ defState CatchOp -> callF "jsCatch"- x -> Left $ "Unsupported PrimOp: " ++ showOutputable x+ x -> Left $ "Unsupported PrimOp: " ++ showOutputable cfg x where (arr:ix:_) = xs
− src/Haste/Util.hs
@@ -1,13 +0,0 @@-{-# LANGUAGE CPP #-}--- | Misc. utility functions.-module Haste.Util where-import DynFlags-import Outputable--#if __GLASGOW_HASKELL__ >= 706-showOutputable :: Outputable a => a -> String-showOutputable = showPpr tracingDynFlags-#else-showOutputable :: Outputable a => a -> String-showOutputable = showPpr-#endif
src/Haste/Version.hs view
@@ -10,7 +10,7 @@ import Haste.Environment (hasteDir) hasteVersion :: Version-hasteVersion = Version [0, 2, 99] []+hasteVersion = Version [0, 3] [] ghcVersion :: String ghcVersion = cProjectVersion
src/Main.hs view
@@ -15,7 +15,6 @@ import System.Environment (getArgs) import Control.Monad (when) import Haste-import Haste.Util (showOutputable) import Haste.Environment import Haste.Version import Args@@ -30,6 +29,7 @@ import Data.List import Data.String import qualified Data.ByteString.Char8 as B+import qualified Control.Shell as Sh logStr :: String -> IO () logStr = hPutStrLn stderr@@ -119,12 +119,12 @@ case argRes of -- We got --help as an argument - display help and exit. Left help -> putStrLn help- + -- We got a config and a set of arguments for GHC; let's compile! Right (cfg, ghcargs) -> do -- Parse static flags, but ignore profiling. (ghcargs', _) <- parseStaticFlags [noLoc a | a <- ghcargs, a /= "-prof"]- + runGhc (Just libdir) $ handleSourceError (const $ liftIO exitFailure) $ do -- Handle dynamic GHC flags. Make sure __HASTE__ is #defined. let args = "-D__HASTE__" : map unLoc ghcargs'@@ -142,33 +142,62 @@ _ <- load LoadAllTargets depanal [] False mapM_ (compile cfg dynflags') deps- + -- Link everything together into a .js file. when (performLink cfg) $ liftIO $ do flip mapM_ files' $ \file -> do- logStr $ "Linking " ++ outFile cfg file+ let outfile = outFile cfg cfg file+ logStr $ "Linking " ++ outfile #if __GLASGOW_HASKELL__ >= 706 let pkgid = showPpr dynflags $ thisPackage dynflags' #else let pkgid = showPpr $ thisPackage dynflags' #endif link cfg pkgid file- case useGoogleClosure cfg of - Just clopath -> closurize clopath $ outFile cfg file+ case useGoogleClosure cfg of+ Just clopath -> closurize clopath+ outfile+ (useGoogleClosureFlags cfg) _ -> return ()+ when (outputHTML cfg) $ do+ res <- Sh.shell $ Sh.withCustomTempFile "." $ \tmp h -> do+ prog <- Sh.file outfile+ Sh.hPutStrLn h (htmlSkeleton outfile prog)+ Sh.liftIO $ hClose h+ Sh.mv tmp outfile+ case res of+ Right () -> return ()+ Left err -> error $ "Couldn't output HTML file: " ++ err +-- | Produce an HTML skeleton with an embedded JS program.+htmlSkeleton :: FilePath -> String -> String+htmlSkeleton filename prog = concat [+ "<!DOCTYPE HTML>",+ "<html><head>",+ "<title>", filename , "</title>",+ "<meta charset=\"UTF-8\">",+ "<script type=\"text/javascript\">", prog, "</script>",+ "</head><body></body></html>"]+ -- | Do everything required to get a list of STG bindings out of a module. prepare :: (GhcMonad m) => DynFlags -> ModSummary -> m ([StgBinding], ModuleName) prepare dynflags theMod = do env <- getSession let name = moduleName $ ms_mod theMod+#if __GLASGOW_HASKELL__ >= 707+ mod = ms_mod theMod+#endif pgm <- parseModule theMod >>= typecheckModule >>= desugarModule >>= liftIO . hscSimplify env . coreModule >>= liftIO . tidyProgram env >>= prepPgm env . fst+#if __GLASGOW_HASKELL__ >= 707+ >>= liftIO . coreToStg dynflags mod+#else >>= liftIO . coreToStg dynflags+#endif return (pgm, name) where prepPgm env tidy = liftIO $ do@@ -181,14 +210,16 @@ -- | Run Google Closure on a file.-closurize :: FilePath -> FilePath -> IO ()-closurize cloPath file = do+closurize :: FilePath -> FilePath -> [String] -> IO ()+closurize cloPath file arguments = do logStr $ "Running the Google Closure compiler on " ++ file ++ "..." let cloFile = file `addExtension` ".clo" cloOut <- openFile cloFile WriteMode build <- runProcess "java"- ["-jar", cloPath, "--compilation_level", "ADVANCED_OPTIMIZATIONS",+ (["-jar", cloPath,+ "--compilation_level", "ADVANCED_OPTIMIZATIONS", "--jscomp_off", "globalThis", file]+ ++ arguments) Nothing Nothing Nothing@@ -225,10 +256,12 @@ (pgm, name) <- prepare dynflags modSummary #if __GLASGOW_HASKELL__ >= 706 let pkgid = showPpr dynflags $ modulePackageId $ ms_mod modSummary+ cfg' = cfg {showOutputable = showPpr dynflags} #else let pkgid = showPpr $ modulePackageId $ ms_mod modSummary+ cfg' = cfg {showOutputable = showPpr} #endif- theCode = generate cfg fp pkgid name pgm+ theCode = generate cfg' fp pkgid name pgm liftIO $ logStr $ "Compiling " ++ myName ++ " into " ++ targetpath liftIO $ writeModule targetpath theCode where
src/haste-boot.hs view
@@ -18,6 +18,7 @@ import Control.Shell import Data.Char (isDigit) import Control.Monad.IO.Class (liftIO)+import Args downloadFile :: String -> Shell BS.ByteString downloadFile f = do@@ -37,40 +38,67 @@ getLibs :: Bool, getClosure :: Bool, useLocalLibs :: Bool,- tracePrimops :: Bool+ tracePrimops :: Bool,+ forceBoot :: Bool } +defCfg :: Cfg+defCfg = Cfg {+ getLibs = True,+ getClosure = True,+ useLocalLibs = False,+ tracePrimops = False,+ forceBoot = False+ }++specs :: [ArgSpec Cfg]+specs = [+ ArgSpec { optName = "force",+ updateCfg = \cfg _ -> cfg {forceBoot = True},+ info = "Re-boot Haste even if already properly booted."},+ ArgSpec { optName = "local",+ updateCfg = \cfg _ -> cfg {useLocalLibs = True},+ info = "Use libraries from source repository rather than " +++ "downloading a matching set from the Internet. " +++ "This is nearly always necessary when installing " +++ "Haste from Git rather than from Hackage. " +++ "When using --local, your current working directory " +++ "must be the root of the Haste source tree."},+ ArgSpec { optName = "no-closure",+ updateCfg = \cfg _ -> cfg {getClosure = False},+ info = "Don't download Closure compiler. You won't be able " +++ "to use --opt-google-closure, unless you manually " +++ "give it the path to compiler.jar."},+ ArgSpec { optName = "no-libs",+ updateCfg = \cfg _ -> cfg {getLibs = False},+ info = "Don't install any libraries. This is probably not " +++ "what you want."},+ ArgSpec { optName = "trace-primops",+ updateCfg = \cfg _ -> cfg {tracePrimops = True},+ info = "Build standard libs for tracing of primitive " +++ "operations. Only use if you're debugging the code " +++ "generator."}+ ]+ main :: IO () main = do args <- getArgs- -- Always get base and closure when forced unless explicitly asked not to;- -- if not forced, get base and closure when necessary, unless asked not to.- let forceBoot = elem "--force" args- libs = if elem "--no-libs" args- then False- else forceBoot || needsReboot- closure = if elem "--no-closure" args- then False- else forceBoot || needsReboot- local = elem "--local" args- trace = elem "--trace-primops" args- cfg = Cfg {- getLibs = libs,- getClosure = closure,- useLocalLibs = local,- tracePrimops = trace- }-- when (needsReboot || forceBoot) $ do- res <- shell $ if local- then bootHaste cfg "."- else withTempDirectory "haste" $ bootHaste cfg- case res of- Right _ -> return ()- Left err -> putStrLn err >> exitFailure+ case handleArgs defCfg specs args of+ Right (cfg, _) -> do+ when (needsReboot || forceBoot cfg) $ do+ res <- shell $ if useLocalLibs cfg+ then bootHaste cfg "."+ else withTempDirectory "haste" $ bootHaste cfg+ case res of+ Right _ -> return ()+ Left err -> putStrLn err >> exitFailure+ Left halp -> do+ putStrLn halp bootHaste :: Cfg -> FilePath -> Shell () bootHaste cfg tmpdir = inDirectory tmpdir $ do+ removeBootFile <- isFile bootFile+ when removeBootFile $ rm bootFile when (getLibs cfg) $ do when (not $ useLocalLibs cfg) $ do fetchLibs tmpdir@@ -124,7 +152,7 @@ inDirectory "libraries" $ do -- Install ghc-prim inDirectory "ghc-prim" $ do- hasteInst ["configure"]+ hasteInst ["configure", "--solver", "topdown"] hasteInst $ ["build", "--install-jsmods"] ++ ghcOpts run_ hasteInstHisBinary ["ghc-prim-0.3.0.0", "dist" </> "build"] "" run_ hastePkgBinary ["update", "packageconfig"] ""@@ -132,7 +160,7 @@ -- Install integer-gmp; double install shouldn't be needed anymore. run_ hasteCopyPkgBinary ["Cabal"] "" inDirectory "integer-gmp" $ do- hasteInst ("install" : ghcOpts)+ hasteInst ("install" : "--solver" : "topdown" : ghcOpts) -- Install base inDirectory "base" $ do@@ -142,15 +170,16 @@ . filter (not . null) . filter (and . zipWith (==) "version") . lines- hasteInst ["configure"]+ hasteInst ["configure", "--solver", "topdown"] hasteInst $ ["build", "--install-jsmods"] ++ ghcOpts let base = "base-" ++ basever pkgdb = "--package-db=dist" </> "package.conf.inplace" run_ hasteInstHisBinary [base, "dist" </> "build"] "" run_ hasteCopyPkgBinary [base, pkgdb] ""+ cpDir "include" hasteDir - -- Install array, fursuit and haste-lib- forM_ ["array", "fursuit", "haste-lib"] $ \pkg -> do+ -- Install array and haste-lib+ forM_ ["array", "haste-lib"] $ \pkg -> do inDirectory pkg $ hasteInst ("install" : ghcOpts) where ghcOpts = concat [