-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathMain.hs
105 lines (90 loc) · 3.83 KB
/
Main.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
-- | This example implements a generator for randomized fake data, which
-- takes its instructions in the form of a PureScript program.
--
-- It takes a filename as its first command line argument, and a random
-- seed as an integer-valued second argument. The input file should contain
-- PureScript source for a module which defines a @main@
-- value for the expression which builds randomized outputs.
--
-- It can be run on the command line as follows:
--
-- @
-- cat > build.purs
-- module Main where
-- import Choose (choose)
-- main = { foo: choose [1, 2, 3] }
-- ^D
--
-- fake-data build.purs 12345
-- @
module Main where
import Control.Monad.IO.Class (MonadIO(..))
import Data.Aeson qualified as Aeson
import Data.Aeson.Encode.Pretty qualified as Pretty
import Data.ByteString.Lazy.Char8 qualified as BL8
import Data.Foldable (traverse_)
import Data.IORef (IORef, atomicModifyIORef', newIORef)
import Data.Text.IO qualified as Text
import Data.Tuple (swap)
import Data.Vector ((!))
import Dovetail
import Dovetail.Aeson qualified as JSON
import Dovetail.FFI.Builder qualified as FFI
import Dovetail.Prelude (stdlib)
import Language.PureScript qualified as P
import Language.PureScript.CoreFn qualified as CoreFn
import System.Environment (getArgs)
import System.Exit (die)
import System.Random qualified as Random
type Context = IORef Random.StdGen
main :: IO ()
main = do
-- Read the module filename from the CLI arguments, and read the module source
[moduleFile, seedString] <- getArgs
moduleText <- Text.readFile moduleFile
let seed = read seedString :: Int
-- This helper function assists in writing the following code in a more
-- direct style:
let orDie e f = e >>= either (liftIO . die . f) pure
-- Create a deterministic random generator from the seed input
gen <- newIORef (Random.mkStdGen seed)
-- The interpretation of our PureScript module is a JSON value, whose
-- computation may involve side-effects in the 'M' monad.
let buildResult
:: IO (Either (InterpretError ()) Aeson.Value)
buildResult = runInterpret () do
traverse_ ffi stdlib
-- Include the JSON library, in case the user wants to return
-- nulls using 'JSON.Nullable'.
_ <- JSON.stdlib
-- This example defines a single interesting function on the
-- Haskell side: the @choose@ function demonstrates the idea of using
-- side-effects in the interpreter - @choose@ chooses randomly between one of
-- its inputs, and returns the selected value
--
-- For random number generation, we use a state monad whose state tracks a
-- standard deterministic generator.
ffi $ FFI.evalFFIBuilder (P.ModuleName "Choose") do
FFI.foreignImport (P.Ident "choose")
(\a -> array a ~> a)
\xs -> do
idx <- liftIO (atomicModifyIORef' gen (swap . Random.randomR (0, length xs - 1)))
pure (xs ! idx)
CoreFn.Module{ CoreFn.moduleName } <- build moduleText
-- Evaluate "main", returning JSON
JSON.evalJSON (Just moduleName) "main"
-- Interpret the main function of the PureScript module as a non-deterministic
-- JSON result
output <- buildResult `orDie` renderInterpretError defaultTerminalRenderValueOptions
-- Render the output as pretty-printed JSON on standard output.
BL8.putStrLn (Pretty.encodePretty output)