Skip to content

Commit

Permalink
.
Browse files Browse the repository at this point in the history
  • Loading branch information
janm399 committed Jan 29, 2014
1 parent 0a7abcf commit dd1623b
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 35 deletions.
3 changes: 2 additions & 1 deletion core/main/Custom/Generator.hs
Expand Up @@ -4,8 +4,9 @@ import Prelude hiding (lookup)
import Custom.Codegen import Custom.Codegen
import Custom.Emit import Custom.Emit
import Custom.Syntax import Custom.Syntax
import GeneratorSupport


mkGenerator :: [Expr] -> ([Int] -> IO b) -> IO b mkGenerator :: [Expr] -> GeneratorFunction Int b
mkGenerator expr f = do mkGenerator expr f = do
_ <- codegen (emptyModule "jit") expr _ <- codegen (emptyModule "jit") expr
f [] f []
75 changes: 46 additions & 29 deletions core/main/Generator.hs
@@ -1,5 +1,5 @@
{-# LANGUAGE OverloadedStrings, BangPatterns #-} {-# LANGUAGE OverloadedStrings, BangPatterns #-}
module Generator(generator, Generator(..), GeneratorDelay) where module Generator(generator, permgenGenerator, newPermgen, Generator(..), GeneratorDelay, Permgen, GeneratorValue) where


import Syntax import Syntax
import GeneratorSupport import GeneratorSupport
Expand All @@ -16,52 +16,69 @@ import qualified Custom.Generator as CG


import qualified Data.Map as M import qualified Data.Map as M


type GeneratorValue = Int

-- |Permgen space holding the prepared generators given the generating expression -- |Permgen space holding the prepared generators given the generating expression
type Permgen a b = M.Map Distribution (Generator a b) type Permgen a b = M.Map Distribution (GeneratorFunction a b)


-- |Generator is a type that performs a step of the generation process -- |Generator is a type that performs a step of the generation process
newtype Generator a b = Generator { newtype Generator a b = Generator {


-- |Performs the generator step -- |Performs the generator step
runGenerator :: GeneratorDelay -- ^The delaying function. You can pass in @threadDelay@, for example. runGenerator :: GeneratorDelay -- ^The delaying function. You can pass in @threadDelay@, for example.
-> (a -> IO b) -- ^The operation to execute in every step -> ([a] -> IO b) -- ^The operation to execute in every step
-> IO b -- ^The final result -> IO b -- ^The final result
} }


-- |Perpares new permgen space for the caching generator -- |Perpares new permgen space for the caching generator
newPermgen :: State Distribution (Permgen a b) newPermgen :: Permgen GeneratorValue a
newPermgen = return M.empty newPermgen = M.empty


-- |Prepares a generator by parsing the input string. It returns a value that -- |Prepares a generator by parsing the input string. It returns a generator that
-- you can use to get the generated values. -- you can use to get the values.
-- --
-- Typically, you'd have code similar to -- Typically, you'd have code similar to
-- @ -- @
-- case (generator "{evendistr 5 [1..20]} every 50ms") of -- case (generator "{evendistr 5 [1..20]} every 50ms") of
-- Left err -> ... -- what do you want, a cookie? -- Left err -> ... -- what do you want, a cookie?
-- Right gen -> runGenerator gen threadDelay (\nums -> ...) -- good user, have numbers -- Right gen -> runGenerator gen threadDelay (\nums -> ...) -- good user, have numbers
-- @ -- @
generator :: String -- ^The expression to parse generator :: String -- ^The expression to parse
-> Either ParseError (Generator [Int] b) -- ^The result with errors or the ready Generator -> Either ParseError (Generator GeneratorValue a) -- ^The result with errors or the ready Generator
generator input = do generator input = permgenGenerator newPermgen input >>= return . snd

-- |Prepares a generator by looking up existing generator in @permgen@; if not found, it proceeds to parse the
-- input string and attempting to build a new generator. It returns a new permgen and a generator that
-- you can use to get the values.
permgenGenerator :: Permgen GeneratorValue a
-> String
-> Either ParseError (Permgen GeneratorValue a, Generator GeneratorValue a)
permgenGenerator permgen input = do
(Expression exp rep del) <- P.parseExpression input (Expression exp rep del) <- P.parseExpression input
gen <- case exp of case M.lookup exp permgen of
Standard body -> SG.mkGenerator <$> SP.parseToplevel body Just genF -> return $ (permgen, buildGenerator genF rep del)
Custom body -> CG.mkGenerator <$> CP.parseToplevel body Nothing -> do { gen <- newGenerator exp

; let permgen' = M.insert exp gen permgen
return $ Generator { runGenerator = \sleep -> \f -> ; return $ (permgen', buildGenerator gen rep del)
case rep of }
Forever -> forever (delayed del sleep (gen f)) where
Times r -> do { t <- fromRange r; times t (delayed del sleep (gen f)) } newGenerator :: Distribution -> Either ParseError (GeneratorFunction GeneratorValue b)
} newGenerator (Standard body) = SG.mkGenerator <$> SP.parseToplevel body
newGenerator (Custom body) = CG.mkGenerator <$> CP.parseToplevel body


buildGenerator :: (GeneratorFunction GeneratorValue b) -> Repetition -> Delay -> Generator GeneratorValue b
buildGenerator gen rep del = Generator { runGenerator = \sleep -> \f ->
case rep of
Forever -> forever (delayed del sleep (gen f))
Times r -> do { t <- fromRange r; times t (delayed del sleep (gen f)) }
}


times :: (Monad m) => Int -> m a -> m a times :: (Monad m) => Int -> m a -> m a
times 1 m = m times 1 m = m
times x m = m >> times (x - 1) m times x m = m >> times (x - 1) m


delayed :: Delay -> GeneratorDelay -> IO a -> IO a delayed :: Delay -> GeneratorDelay -> IO a -> IO a
delayed (Fixed delayRange) sleep !m = do delayed (Fixed delayRange) sleep !m = do
delay <- fromRange delayRange delay <- fromRange delayRange
sleep delay sleep delay
m m
5 changes: 4 additions & 1 deletion core/main/GeneratorSupport.hs
@@ -1,4 +1,4 @@
module GeneratorSupport(fromRange, GeneratorDelay) where module GeneratorSupport(fromRange, GeneratorDelay, GeneratorFunction) where


import Syntax import Syntax
import System.Random (randomIO) import System.Random (randomIO)
Expand All @@ -8,6 +8,9 @@ import Control.Applicative ((<$>))
-- A suitable value is @threadDelay@ -- A suitable value is @threadDelay@
type GeneratorDelay = Int -> IO () type GeneratorDelay = Int -> IO ()


-- |Function that can be applied to some callback to generate the values
type GeneratorFunction a b = (([a] -> IO b) -> IO b)

-- |Picks one random value from the given range -- |Picks one random value from the given range
fromRange :: Range -> IO Int fromRange :: Range -> IO Int
fromRange (Exact x) = return x fromRange (Exact x) = return x
Expand Down
2 changes: 1 addition & 1 deletion core/main/Standard/Generator.hs
Expand Up @@ -5,7 +5,7 @@ import Control.Monad (replicateM)
import Standard.Syntax import Standard.Syntax
import GeneratorSupport import GeneratorSupport


mkGenerator :: Expr -> ([Int] -> IO b) -> IO b mkGenerator :: Expr -> GeneratorFunction Int b
mkGenerator (EvenDistr countRange valueRange) !f = do mkGenerator (EvenDistr countRange valueRange) !f = do
count <- fromRange countRange count <- fromRange countRange
values <- replicateM count (fromRange valueRange) values <- replicateM count (fromRange valueRange)
Expand Down
11 changes: 11 additions & 0 deletions core/test/Custom/GeneratorSpec.hs
Expand Up @@ -2,9 +2,20 @@ module Custom.GeneratorSpec(spec) where


import Test.Hspec import Test.Hspec
import GeneratorSpecSupport import GeneratorSpecSupport
import Generator (newPermgen)
import qualified Data.Map as M


spec :: Spec spec :: Spec
spec = do spec = do
describe "Do expression" $ do describe "Do expression" $ do
it "Generates fixed count of ranges" $ do it "Generates fixed count of ranges" $ do
generate' "do { def foo(a b) a * b; foo(1, 1); } once" `shouldReturn` [1] generate' "do { def foo(a b) a * b; foo(1, 1); } once" `shouldReturn` [1]

describe "Permgen generator" $ do
it "Makes use of the permgen space" $ do
let expr = "do { def foo(a b) a * b; foo(1, 1); } once"
(permgen1, values1) <- permgenGenerate' newPermgen expr
(_, values2) <- permgenGenerate' permgen1 expr

print (M.keys permgen1)
values1 `shouldBe` values2
9 changes: 7 additions & 2 deletions core/test/GeneratorSpecSupport.hs
@@ -1,4 +1,4 @@
module GeneratorSpecSupport(generate', error', avg) where module GeneratorSpecSupport(generate', permgenGenerate', error', avg) where


import Generator import Generator
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
Expand All @@ -8,10 +8,15 @@ error' expr =
let Left err = generator expr let Left err = generator expr
in show err in show err


generate' :: String -> IO [Int] generate' :: String -> IO [GeneratorValue]
generate' expr = generate' expr =
let Right f = runGenerator <$> generator expr let Right f = runGenerator <$> generator expr
in f (const $ return ()) return in f (const $ return ()) return


permgenGenerate' :: Permgen GeneratorValue [GeneratorValue] -> String -> IO (Permgen GeneratorValue [GeneratorValue], [GeneratorValue])
permgenGenerate' permgen expr =
let Right (permgen', gen) = permgenGenerator permgen expr
in (runGenerator gen (const $ return ()) return) >>= (\nums -> return (permgen', nums))

avg :: [Int] -> Int avg :: [Int] -> Int
avg xs = sum xs `div` length xs avg xs = sum xs `div` length xs
1 change: 1 addition & 0 deletions hwsexp.cabal
Expand Up @@ -48,6 +48,7 @@ test-suite core-spec
hwsexp hwsexp
, base >= 4.6 , base >= 4.6
, parsec >= 3.1.3 , parsec >= 3.1.3
, containers >= 0.5.0.0
, hspec >= 1.3 , hspec >= 1.3


executable ws executable ws
Expand Down
2 changes: 1 addition & 1 deletion ws/main/Main.hs
Expand Up @@ -53,7 +53,7 @@ application state pending = do
-- |Performs the query on behalf of the client, cleaning up after itself when the client disconnects -- |Performs the query on behalf of the client, cleaning up after itself when the client disconnects
perform :: MVar ServerState -- ^ The server state perform :: MVar ServerState -- ^ The server state
-> Client -- ^ The client tuple (the query to perform and the connection for the responses) -> Client -- ^ The client tuple (the query to perform and the connection for the responses)
-> Generator [Int] () -- ^ The value generator -> Generator Int () -- ^ The value generator
-> IO () -- ^ The output -> IO () -- ^ The output
perform state client@(query, conn) gen = handle catchDisconnect $ do perform state client@(query, conn) gen = handle catchDisconnect $ do
runGenerator gen threadDelay (\numbers -> WS.sendTextData conn (T.pack $ show numbers)) runGenerator gen threadDelay (\numbers -> WS.sendTextData conn (T.pack $ show numbers))
Expand Down

0 comments on commit dd1623b

Please sign in to comment.