sketch-frp-copilot-1.0.1: src/Sketch/FRP/Copilot/Internals.hs
module Sketch.FRP.Copilot.Internals where
import Sketch.FRP.Copilot.Types
import Language.Copilot
import Control.Monad.Writer
import Control.Monad.State.Strict
import Data.Functor.Identity
import qualified Data.Map as M
import qualified Data.Set as S
import Data.Maybe
getTriggerLimit :: TriggerLimit -> Behavior Bool
getTriggerLimit (TriggerLimit b) = b
getTriggerLimit NoTriggerLimit = true
addTriggerLimit :: TriggerLimit -> Behavior Bool -> Behavior Bool
addTriggerLimit tl c = getTriggerLimit (tl <> TriggerLimit c)
-- | Gets a unique id.
getUniqueId :: String -> GenSketch ctx UniqueId
getUniqueId s = do
UniqueIds m <- get
let u = maybe 1 succ (M.lookup s m)
put $ UniqueIds $ M.insert s u m
return (UniqueId u)
-- | Generates a unique name.
uniqueName :: String -> UniqueId -> String
uniqueName s (UniqueId i)
| i Prelude.== 1 = s
| otherwise = s <> "_" <> show i
uniqueName' :: String -> UniqueId -> String
uniqueName' s (UniqueId i) = s <> "_" <> show i
-- | Use to create an empty framework.
--
-- It helps to specify the type of context to use:
--
-- > (emptyFramework @Arduino) { ... }
emptyFramework :: Context ctx => GenFramework ctx
emptyFramework = mempty
mkCChunk :: [CLine] -> [CChunk]
mkCChunk l = [CChunk l]
-- | Copilot only supports calling a trigger with a given name once
-- per Spec; the generated C code will fail to build if the same name is
-- used in two triggers. This generates a unique alias that can be
-- used in a trigger.
defineTriggerAlias
:: String
-> GenFramework ctx
-> GenSketch ctx (GenFramework ctx, String)
defineTriggerAlias = defineTriggerAlias' ""
defineTriggerAlias'
:: String
-> String
-> GenFramework ctx
-> GenSketch ctx (GenFramework ctx, String)
defineTriggerAlias' suffix cfuncname f = do
let basetname = if null suffix
then cfuncname
else cfuncname <> "_" <> suffix
u <- getUniqueId basetname
let triggername = uniqueName basetname u
let define = if cfuncname Prelude./= triggername
then mkCChunk [ CLine $ "#define " <> triggername <> " " <> cfuncname ]
else mempty
return (f { defines = define <> defines f }, triggername)
data MkInputSource ctx t = InputSource
{ defineVar :: [CChunk]
-- ^ Added to the `Framework`'s `defines`, this typically
-- defines a C variable.
, setupInput :: [CChunk]
-- ^ How to set up the input, not including pin mode.
, inputPinmode :: M.Map ctx PinMode
-- ^ How pins are used by the input.
, readInput :: [CChunk]
-- ^ How to read a value from the input, this typically
-- reads a value into a C variable.
, inputStream :: Stream t
-- ^ How to use Copilot's extern to access the input values.
}
mkInput :: MkInputSource ctx t -> GenSketch ctx (Behavior t)
mkInput i = do
u <- getUniqueId "input"
tell [(mkspec u, f u)]
return (inputStream i)
where
f u ratelimited = Framework
{ defines = defineVar i <> mkdefine u ratelimited
, setups = setupInput i
, earlySetups = mempty
, pinmodes = M.map S.singleton (inputPinmode i)
, loops = mkloops u ratelimited (readInput i)
}
varname = uniqueName "update_input"
triggername = uniqueName "input"
mkdefine _ NoTriggerLimit = []
mkdefine u (TriggerLimit _) = mkCChunk $ map CLine
[ "bool " <> varname u <> " = true;"
, "void " <> triggername u <> " (bool v) {"
, " " <> varname u <> " = v;"
, "}"
]
mkloops _ NoTriggerLimit reader = reader
mkloops u (TriggerLimit _) reader = mkCChunk $ concat
[ [ CLine $ "if (" <> varname u <> ") {" ]
, map (\(CLine l) -> CLine $ " " <> l ) readerlines
, [ CLine "}" ]
]
where
readerlines = concatMap (\(CChunk l) -> l) reader
mkspec _ NoTriggerLimit = return ()
mkspec u (TriggerLimit c) = trigger (triggername u) true [arg c]
evalSketch :: Context ctx => GenSketch ctx a -> (Maybe Spec, GenFramework ctx)
evalSketch (GenSketch s) = (spec, f)
where
(is, fs) = unzip $
runIdentity $ evalStateT (execWriterT s) (UniqueIds mempty)
f = mconcat (map (\f' -> f' NoTriggerLimit) fs)
-- Copilot will throw an ugly error if given a spec that does
-- nothing at all, so return Nothing to avoid that.
spec :: Maybe Spec
spec = if null is
then Nothing
else Just $ sequence_ $ map (\i -> i NoTriggerLimit) is
-- | Extracts a copilot `Spec` from a `Sketch`.
--
-- This can be useful to intergrate with other libraries
-- such as copilot-theorem.
sketchSpec :: Context ctx => GenSketch ctx a -> Spec
sketchSpec = fromMaybe (return ()) . fst . evalSketch