Repository navigation
Expand file tree
/
Copy pathDynamic.hs
More file actions
253 lines (216 loc) · 8.62 KB
/
Copy pathDynamic.hs
File metadata and controls
253 lines (216 loc) · 8.62 KB
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- |
-- Copyright : (c) NASA, 2024-2025
-- License : BSD-style (see the LICENSE file in the distribution)
--
-- Allow loading Copilot specifications from strings and modifying them live.
--
-- An example of a spec that can be passed as argument to the live simulator
-- follows:
--
-- @
-- spec :: String
-- spec = unlines
-- [ "let temperature :: Stream Word8"
-- , " temperature = extern \"temperature\" (Just [0, 15, 20, 25, 30])"
-- , ""
-- , " ctemp :: Stream Float"
-- , " ctemp = (unsafeCast temperature) * (150.0 / 255.0) - 50.0"
-- , ""
-- , " trueFalse :: Stream Bool"
-- , " trueFalse = [True] ++ not trueFalse"
-- , ""
-- , "in do trigger \"heaton\" (temperature < 18) [arg ctemp, arg (constI16 1), arg trueFalse]"
-- , " trigger \"heatoff\" (temperature > 21) [arg (constI16 1), arg ctemp]"
-- , " observer \"temperature\" temperature"
-- , " observer \"temperature2\" (temperature + 1)"
-- ]
-- @
--
-- The imports are predefined.
module Copilot.Visualize.Dynamic
( simInit
, simStep
, SimulationSettings(..)
, mkDefaultSimulationSettings
, SimData(..)
)
where
-- External imports
import qualified Language.Haskell.Interpreter as HI
import Prelude hiding (div, not, (++), (<), (>))
import qualified Prelude
-- External imports: Copilot
import qualified Copilot.Core as Core
import Copilot.Language hiding (interpret, typeOf)
import Language.Copilot hiding (interpret, typeOf)
-- Internal imports
import Copilot.Visualize.TypedTrace
-- | Simulation data.
data SimData = SimData
{ simSteps :: Int -- ^ Number of steps to simulate.
, simSpec :: Core.Spec -- ^ Copilot spec to simulate.
, simString :: String -- ^ Textual representation of Copilot spec.
}
-- | Initialize a simulation.
simInit :: SimulationSettings -> String -> IO SimData
simInit simulationSettings spec = do
-- Load the spec, and save it so that it doesn't need to be reloaded if it
-- doesn't change.
let numSteps = simulationSettingsInitialSteps simulationSettings
spec' <- readSpec simulationSettings spec
let simData = SimData numSteps spec' spec
return simData
-- | Update the spec based on the input command received.
simStep :: SimulationSettings
-> SimData
-> (Command, String)
-> IO SimData
simStep _ simData (StepUp, _) =
pure simData { simSteps = simSteps simData + 1 }
simStep _ simData (StepDown, _) =
pure simData { simSteps = simSteps simData + 1 }
simStep simulationSettings simData (AddStream name expr, _) = do
let specN = concat [ simString simData, "\n", " ", completeExpr]
completeExpr = concat [ "observer ", show name, " (", expr, ")" ]
let trace = extractTrace $ simSpec simData
spec2 <- readSpec simulationSettings specN
let spec3 = updateWithTrace trace spec2
pure simData { simSpec = spec3, simString = specN }
simStep simulationSettings simData (command, name) = do
spec' <- apply simulationSettings (simSpec simData) name command
pure simData { simSpec = spec' }
-- | Settings used for a simulation.
data SimulationSettings = SimulationSettings
{ simulationSettingsInitialSteps :: Int
, simulationSettingsImports :: [(String, Maybe String)]
}
-- | Default settings that simulates 3 steps and provides default imports of
-- the main Copilot modules.
mkDefaultSimulationSettings :: SimulationSettings
mkDefaultSimulationSettings =
SimulationSettings
3
[ ("Control.Monad.Writer", Nothing)
, ("Copilot.Language", Nothing)
, ("Copilot.Language.Spec", Nothing)
, ("Data.Functor.Identity", Nothing)
, ("Language.Copilot", Nothing)
, ("Prelude", Just "P")
]
-- * Commands
-- * Load specs from strings
-- | Read a specification from a string and reify it.
readSpec :: SimulationSettings -> String -> IO Core.Spec
readSpec settings spec = do
r <- HI.runInterpreter (loadSpec settings spec)
case r of
Left err -> do putStrLn $ "Error: " Prelude.++ show err
error $ show err
Right s -> return s
-- | Load a spec from a string, adding standard imports.
loadSpec :: SimulationSettings -> String -> HI.Interpreter Core.Spec
loadSpec settings spec = do
HI.setImportsQ (simulationSettingsImports settings)
spec' <- HI.interpret spec (HI.as :: Spec)
HI.liftIO $ reify spec'
-- | Commands to apply to a running simulation.
data Command = StepUp
| StepDown
| Up Int
| Down Int
| AddStream String String
| Noop
deriving (Eq, Read, Show)
-- | Apply a command to a simulation.
apply :: SimulationSettings -> Core.Spec -> String -> Command -> IO Core.Spec
apply settings spec _name (AddStream sName sExpr) = do
spec' <- addStream settings sName sExpr
-- TODO: I need to bring the streams from the other spec too, otherwise the
-- streams to include may refer to streams by ID that are in a different
-- scope.
let observers' = Core.specObservers spec'
observers = Core.specObservers spec
return $ spec { Core.specObservers = observers Prelude.++ observers' }
apply _settings spec name command = pure $ spec
{ Core.specStreams =
map (updateStream name command) (Core.specStreams spec)
, Core.specObservers =
map (updateObserver name command) (Core.specObservers spec)
, Core.specTriggers =
map (updateTrigger name command) (Core.specTriggers spec)
}
-- | Apply a command to an observer.
updateObserver :: String -> Command -> Core.Observer -> Core.Observer
updateObserver name command (Core.Observer i e ty) =
Core.Observer i (updateExpr name command e) ty
-- | Apply a command to a trigger.
updateTrigger :: String -> Command -> Core.Trigger -> Core.Trigger
updateTrigger name command (Core.Trigger i e es) = Core.Trigger i e' es'
where
e' = updateExpr name command e
es' = map (updateUExpr name command) es
-- | Apply a command to a core expression.
updateExpr :: String -> Command -> Core.Expr a -> Core.Expr a
updateExpr name command expr = case expr of
Core.ExternVar ty nameE vs
| nameE Prelude.== name
-> Core.ExternVar ty nameE (updateValues vs ty command)
| otherwise
-> expr
Core.Op1 op expr1 ->
Core.Op1 op (updateExpr name command expr1)
Core.Op2 op expr1 expr2 ->
Core.Op2 op (updateExpr name command expr1) (updateExpr name command expr2)
Core.Op3 op expr1 expr2 expr3 ->
Core.Op3
op
(updateExpr name command expr1)
(updateExpr name command expr2)
(updateExpr name command expr3)
_ -> expr
-- | Apply a command to an untyped expression that carries the information
-- about the type of the expression as a value (existential).
updateUExpr :: String -> Command -> Core.UExpr -> Core.UExpr
updateUExpr name cmd (Core.UExpr ty expr) =
Core.UExpr ty (updateExpr name cmd expr)
-- | Apply a command to a stream.
updateStream :: String -> Command -> Core.Stream -> Core.Stream
updateStream name command (Core.Stream i buf expr ty) =
Core.Stream i buf (updateExpr name command expr) ty
-- | Apply a command to a series of typed values, if any.
updateValues :: Maybe [a] -> Type a -> Command -> Maybe [a]
updateValues vsM ty command =
fmap (fmap (updateValue command ty) . zip [0..]) vsM
-- | Apply a command to a typed value.
updateValue :: Command -> Type a -> (Int, a) -> a
updateValue (Up n) Core.Bool (ix, a) =
if n Prelude.== ix then Prelude.not a else a
updateValue (Down n) Core.Bool (ix, a) =
if n Prelude.== ix then Prelude.not a else a
updateValue (Up n) Core.Word8 (ix, a) =
if n Prelude.== ix then a + 1 else a
updateValue (Down n) Core.Word8 (ix, a) =
if n Prelude.== ix then a - 1 else a
updateValue _ _ (_, a) = a
-- * Sample spec
-- | Produce a specification from the expression for a stream.
addStream :: SimulationSettings -> String -> String -> IO Core.Spec
addStream settings name expr = do
r <- HI.runInterpreter (addStream' settings name expr)
case r of
Left err -> do putStrLn $ "Error: " Prelude.++ show err
error $ show err
Right spec -> return spec
-- | Produce a specification from the expression for a stream, in an
-- interpreter context.
--
-- Observe that Interpreter () is an alias for InterpreterT IO ()
addStream' :: SimulationSettings -> String -> String -> HI.Interpreter Core.Spec
addStream' settings name expr = do
HI.setImportsQ (simulationSettingsImports settings)
let completeExpr = concat [ "observer ", show name, " (", expr, ")" ]
spec <- HI.interpret completeExpr (HI.as :: Spec)
HI.liftIO $ reify spec