This repository was archived by the owner on Jun 28, 2025. It is now read-only.
Repository navigation
Expand file tree
/
Copy pathUntypedTrace.hs
More file actions
176 lines (151 loc) · 5.71 KB
/
Copy pathUntypedTrace.hs
File metadata and controls
176 lines (151 loc) · 5.71 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
{-# LANGUAGE DeriveGeneric #-}
-- |
-- Copyright : (c) NASA, 2024-2025
-- License : BSD-style (see the LICENSE file in the distribution)
--
-- Abstract representation of a trace as a series named streams with their
-- values, together with functions needed to convert a 'Spec' into a trace.
--
-- This trace is flat (untyped). Information about the types is captured
-- using boolean flags. This kind of trace is most useful to render values
-- on in logicless templates, since they don't have the capability to examine
-- the Haskell types.
module Copilot.Visualize.UntypedTrace
( AppData(..)
, Trace
, TraceElem(..)
, TraceValue(..)
, makeTraceEval
)
where
-- External imports
import Data.Aeson (ToJSON (..))
import Data.Bifunctor (second)
import Data.List (find, transpose)
import Data.Maybe (isJust, isNothing)
import GHC.Generics (Generic)
import Text.Printf (printf)
import Text.Read (readMaybe)
-- External imports: Copilot
import Copilot.Core (Spec (..), triggerArgs, triggerName)
import Copilot.Interpret.Eval (ExecTrace, Output, interpObservers,
interpTriggers)
-- * Abstract representation of a trace
-- | Data related to a specification.
data AppData = AppData
{ adTraceElems :: Trace
, adLastSample :: Int
}
deriving (Generic, Show)
instance ToJSON AppData
-- | A trace, defined by a list of trace elements, each defining a stream (in
-- an abstract sense).
type Trace = [ TraceElem ]
-- | An element of a trace, or stream, with a name, its values, and information
-- about its type, needed to render it correctly.
data TraceElem = TraceElem
{ teName :: String
, teIsBoolean :: Bool
, teIsFloat :: Bool
, teValues :: [ TraceValue ]
}
deriving (Generic, Show)
instance ToJSON TraceElem
-- | An individual sample, with a value and an indication of whether the sample
-- exists.
data TraceValue = TraceValue
{ tvValue :: String
, tvIsEmpty :: Bool
}
deriving (Generic, Show)
instance ToJSON TraceValue
-- | Generate an abstract representation of a trace of a specification
-- interpreted for a given number of steps.
makeTraceEval :: Int
-> Spec
-> ExecTrace
-> AppData
makeTraceEval k spec e =
AppData (observerTEs ++ triggerTEs) (k - 1)
where
observerTEs = map mkTraceElem (interpObserversOpt spec e)
triggerTEs = map mkTraceElem (interpTriggersWithArgs spec e)
-- | Generate an element of a trace from a stream name and its values over
-- time.
mkTraceElem :: (String, [Maybe Output]) -> TraceElem
mkTraceElem (name, outputs) = TraceElem
{ teName = name
, teValues = values
, teIsBoolean = any (isBoolean . tvValue) values
, teIsFloat = any (isFloat . tvValue) values
}
where
values = map mkTraceValue outputs
-- | Generate an individual value (sample) of a trace.
mkTraceValue :: Maybe Output -> TraceValue
mkTraceValue x = TraceValue (showValue x) (isNothing x)
-- * Auxiliary functions
-- | Compute the list of values associated to observers.
interpObserversOpt :: Spec -> ExecTrace -> [(String, [Maybe Output])]
interpObserversOpt _spec = map (second (map Just)) . interpObservers
-- | Compute the list of values associated to triggers and their arguments.
--
-- For each trigger, we first include the values of the trigger itself, and
-- then the values of the arguments to the trigger.
interpTriggersWithArgs :: Spec -> ExecTrace -> [(String, [Maybe Output])]
interpTriggersWithArgs spec = concatMap triggerOutputs . interpTriggers
where
-- This function adds one more output for the trigger itself.
triggerOutputs :: (String, [Maybe [Output]]) -> [(String, [Maybe Output])]
triggerOutputs (triggerName, triggerArgs) =
(triggerName, triggerValues) : zip argNames argValues
where
triggerValues = map triggerValue triggerArgs
-- Value for the trigger at a given time, based on the values of its
-- arguments.
triggerValue :: Maybe [Output] -> Maybe Output
triggerValue Nothing = Just "false"
triggerValue (Just _) = Just "true"
-- Names and values for the arguments.
argNames = map (\ix -> triggerName ++ "Arg" ++ show ix) [0..]
argValues = transpose (transMaybes numArgs triggerArgs)
numArgs = triggerNumArgs spec triggerName
-- Number of arguments to a trigger in a spec.
--
-- PRE: name exists as a trigger in spec.
triggerNumArgs :: Spec -> String -> Int
triggerNumArgs spec name =
case find (\t -> triggerName t == name) (specTriggers spec) of
Nothing -> error "Couldn't find given trigger in spec, should never occur!"
Just t -> length $ triggerArgs t
-- | True if the input value denotes a boolean value.
isBoolean :: String -> Bool
isBoolean "true" = True
isBoolean "false" = True
isBoolean _ = False
-- | True if the input value denotes a floating point value.
isFloat :: String -> Bool
isFloat s = isJust asInt || isJust asFloat
where
asInt :: Maybe Int
asInt = readMaybe s
asFloat :: Maybe Float
asFloat = readMaybe s
-- | Show a value.
showValue :: Maybe Output -> String
showValue Nothing = "--"
showValue (Just s) | isFloat s = showValueFloat s
| otherwise = s
-- | Show a floating point value.
showValueFloat :: Output -> String
showValueFloat = formatFloat . read
where
formatFloat :: Double -> String
formatFloat = printf "%.2g"
-- | Given a list of maybe lists of known length, this function creates a list
-- of lists, pushing the Maybe's inside.
transMaybes :: Int -> [Maybe [a]] -> [[Maybe a]]
transMaybes = map . transMaybes'
where
transMaybes' argsLength (Just xs) = map Just xs
transMaybes' argsLength Nothing = replicate argsLength Nothing