never executed always true always false
1 {-# LANGUAGE FlexibleContexts #-}
2 {-# LANGUAGE ImportQualifiedPost #-}
3 {-# LANGUAGE LambdaCase #-}
4 {-# LANGUAGE NamedFieldPuns #-}
5 {-# LANGUAGE OverloadedStrings #-}
6 {-# OPTIONS_GHC -Wno-type-defaults -Wno-incomplete-uni-patterns #-}
7
8 {- |
9 Module : NITTA.Frontends.Lua
10 Description : Lua frontend prototype
11 Copyright : (c) Aleksandr Penskoi, 2019
12 License : BSD3
13 Maintainer : aleksandr.penskoi@gmail.com
14 Stability : experimental
15
16 This module analyzes an abstract syntax tree of the Lua language source code, provided by Language.Lua module,
17 and stores it into a NITTA's data flow graph.
18
19 Supported Lua costructions are:
20
21 - Simple math operators (addition, subtraction, multiplication and division);
22 - Variable assignments;
23 - Bitwise left and right shifts;
24 - Recursive calls.
25
26 The naming of variables in the output dataflow graph:
27
28 @
29 x^1#2 -- for variables
30 | | |
31 | | +-- value access number (one for each), e.g.
32 | | x = 1; send(x); reg(x) -- two accesses
33 | |
34 | +---- value assignment number (one for each, optional)
35 | x = f(1); x = g(2) -- two assignments
36 |
37 +------ original variable name
38
39 !123#3 -- for constant
40 | |
41 | +--- value access number
42 |
43 +------ value: 123
44
45 _a#4 -- for an unnamed variable (example see below)
46 | |
47 | +--- value access number
48 |
49 +----- char of unnamed variable (a, b..., aa, ab, ...)
50 @
51
52 Example:
53
54 >>> :{
55 void $ mapM print $ functions (frDataFlow $ translateLua $ T.pack $ unlines
56 [ "function f()"
57 , " local a = 1 + 2 + 3"
58 , " local b = a + 4 + 5"
59 , " b = b * 1 + 2"
60 , " c, d = b / 2"
61 , " send(b)"
62 , "end"
63 , "f()"
64 ] :: DataFlowGraph String Int)
65 :}
66 const(1) = !1#0 = !1#1
67 const(2) = !2#0 = !2#1 = !2#2
68 !1#0 + !2#0 = _0#a
69 const(3) = !3#0
70 _0#a + !3#0 = a^0#0
71 const(4) = !4#0
72 a^0#0 + !4#0 = _0#b
73 const(5) = !5#0
74 _0#b + !5#0 = b^0#0
75 b^0#0 * !1#1 = _1#b
76 _1#b + !2#1 = b^1#0 = b^1#1
77 !2#2 / b^1#0 = _; !2#2 mod b^1#0 = _
78 send(b^1#1)
79 -}
80 module NITTA.Frontends.Lua (
81 translateLua,
82 FrontendResult (..),
83 TraceVar (..),
84
85 -- * Internal
86 LuaAlgBuilder (..),
87 LuaStatement (..),
88 LuaValueInstance (..),
89 findStartupFunction,
90 getLuaBlockFromSources,
91 processStatement,
92 ) where
93
94 import Control.Monad.State
95 import Data.HashMap.Strict qualified as HM
96 import Data.Hashable
97 import Data.Maybe
98 import Data.String
99 import Data.String.ToString
100 import Data.Text qualified as T
101 import Language.Lua hiding (Var)
102 import NITTA.Frontends.Common
103 import NITTA.Intermediate.DataFlow
104 import NITTA.Intermediate.Functions qualified as F
105 import NITTA.Intermediate.Types
106 import NITTA.Utils.Base
107
108 getUniqueLuaVariableName LuaValueInstance{lviName, lviIsConstant = True} luaValueAccessCount = "!" <> lviName <> "#" <> showText luaValueAccessCount
109 getUniqueLuaVariableName LuaValueInstance{lviName, lviAssignCount} luaValueAccessCount
110 | T.head lviName == '_' = lviName
111 | otherwise = lviName <> "^" <> showText lviAssignCount <> "#" <> showText luaValueAccessCount
112
113 data LuaStatement x = LuaStatement
114 { fIn :: [T.Text]
115 , fOut :: [LuaValueInstance]
116 , fName :: T.Text
117 , fValues :: [x]
118 , fInt :: [Int]
119 }
120 deriving (Show, Eq)
121
122 -- | Stores information about a particular version of a variable. The version of a variable changes after assigning a new value to it.
123 data LuaValueInstance = LuaValueInstance
124 { lviName :: T.Text
125 , lviAssignCount :: Int
126 , lviIsConstant :: Bool
127 }
128 deriving (Show, Eq)
129
130 instance Hashable LuaValueInstance where
131 hashWithSalt i LuaValueInstance{lviName, lviAssignCount, lviIsConstant} =
132 ( (hashWithSalt i lviName * 31)
133 + hashWithSalt i lviAssignCount
134 )
135 * 31
136 + hashWithSalt i lviIsConstant
137
138 data LuaAlgBuilder x = LuaAlgBuilder
139 { algGraph :: [LuaStatement x]
140 -- ^ A list containing all expressions to be added to the final graph.
141 , algLatestLuaValueInstance :: HM.HashMap T.Text LuaValueInstance
142 -- ^ A table that maps a variable name to the most recent corresponding LuaValueInstance.
143 , algVarCounters :: HM.HashMap T.Text Int
144 -- ^ A table needed to generate unique temporary variable names.
145 , algVars :: HM.HashMap LuaValueInstance [T.Text]
146 -- ^ A table lists all uses of a particular LuaValueInstance.
147 , algStartupArgs :: HM.HashMap Int (T.Text, T.Text)
148 -- ^ Map argument index to the variable name and initial value (in text).
149 , algConstants :: HM.HashMap T.Text LuaValueInstance
150 -- ^ A table correlating constant with LuaValueInstance which store this constant.
151 , algTraceFuncs :: [([T.Text], Maybe T.Text)]
152 -- ^ A list that stores debug information about monitored variables and their display formats.
153 }
154 deriving (Show)
155
156 -- left part of lua statement
157 parseLeftExp (VarName (Name v)) = v
158 parseLeftExp var = error $ "unexpected lua variable declaration format : " <> show var
159
160 -- right part of lua statement
161 parseRightExp [fOut] (Binop ShiftL a (Number IntNum s)) = do
162 varName <- parseExpArg fOut a
163 addVariable [varName] [fOut] [] "shiftL" [readText s]
164 parseRightExp [fOut] (Binop ShiftR a (Number IntNum s)) = do
165 varName <- parseExpArg fOut a
166 addVariable [varName] [fOut] [] "shiftR" [readText s]
167 parseRightExp [fOut] (Number _ valueString) = do
168 addVariable [] [fOut] [readText valueString] "constant" []
169 parseRightExp fOut@(x : _) (Binop op a b) = do
170 varNameA <- parseExpArg x a
171 varNameB <- parseExpArg x b
172 addVariable [varNameA, varNameB] fOut [] (getBinopFuncName op) []
173 where
174 getBinopFuncName Add = "add"
175 getBinopFuncName Sub = "sub"
176 getBinopFuncName Mul = "multiply"
177 getBinopFuncName Div = "divide"
178 getBinopFuncName o = error $ "unknown binop: " <> show o
179 parseRightExp fOut (PrefixExp (Paren e)) = parseRightExp fOut e
180 parseRightExp fOut (Unop Neg (Number numType name)) = parseRightExp fOut (Number numType ("-" <> name))
181 parseRightExp [fOut] (Unop Neg expr@(PrefixExp _)) = do
182 varName <- parseExpArg fOut expr
183 addVariable [varName] [fOut] [] "neg" []
184 parseRightExp
185 [fOut]
186 ( PrefixExp
187 ( PEFunCall
188 ( NormalFunCall
189 (PEVar (VarName (Name fname)))
190 (Args args)
191 )
192 )
193 ) = do
194 fIn <- mapM (parseExpArg fOut) args
195 addFunction fname fIn [fOut]
196 parseRightExp [fOut] (PrefixExp (PEVar (VarName (Name name)))) = do
197 addAlias fOut name
198 parseRightExp _ expr = error $ "unknown expression : " <> show expr
199
200 parseExpArg _ n@(Number _ _) = do
201 addConstant n
202 parseExpArg fOut expr@(Unop Neg _) = do
203 name <- getNextTmpVarName fOut
204 _ <- parseRightExp [name] expr
205 addVariableAccess name
206 parseExpArg _ (PrefixExp (PEVar (VarName (Name name)))) = do
207 addVariableAccess name
208 parseExpArg fOut binop@Binop{} = do
209 name <- getNextTmpVarName fOut
210 _ <- parseRightExp [name] binop
211 addVariableAccess name
212 parseExpArg fOut (PrefixExp (Paren arg)) = parseExpArg fOut arg
213 parseExpArg fOut call@(PrefixExp (PEFunCall _)) = do
214 name <- getNextTmpVarName fOut
215 _ <- parseRightExp [name] call
216 addVariableAccess name
217 parseExpArg _ _ = undefined
218
219 getNextTmpVarName fOut
220 | T.isInfixOf "#" fOut = getNextTmpVarName (T.splitOn "#" fOut !! 1)
221 | otherwise = do
222 luaAlgBuilder@LuaAlgBuilder{algVarCounters} <- get
223 case HM.lookup fOut algVarCounters of
224 Just value -> do
225 put luaAlgBuilder{algVarCounters = HM.insert fOut (value + 1) algVarCounters}
226 return $ "_" <> showText value <> "#" <> fOut
227 Nothing -> do
228 put luaAlgBuilder{algVarCounters = HM.insert fOut 1 algVarCounters}
229 return $ "_0#" <> fOut
230
231 addStartupFuncArgs (FunCall (NormalFunCall _ (Args exps))) (FunAssign _ (FunBody names _ _)) = do
232 mapM_
233 ( \case
234 (Name name, Number _ valueString, serialNumber) -> addToBuffer name valueString serialNumber
235 _ -> error "addStartupFuncArgs: internal error"
236 )
237 $ zip3 names exps [0 ..]
238 return ""
239 where
240 addToBuffer name valueString serialNumber = do
241 luaAlgBuilder@LuaAlgBuilder{algVars, algLatestLuaValueInstance, algStartupArgs} <- get
242 let value = LuaValueInstance{lviName = name, lviAssignCount = 0, lviIsConstant = False}
243 put luaAlgBuilder{algLatestLuaValueInstance = HM.insert name value algLatestLuaValueInstance, algVars = HM.insert value [] algVars, algStartupArgs = HM.insert serialNumber (name, valueString) algStartupArgs}
244 return value
245 addStartupFuncArgs _ _ = undefined
246
247 -- Lua language Stat structure parsing
248 -- LocalAssign
249 processStatement _ (LocalAssign _names Nothing) = do
250 return ()
251 processStatement fn (LocalAssign names (Just exps)) =
252 processStatement fn $ Assign (map VarName names) exps
253 -- Assign
254 processStatement fn (Assign lexps@[_] [Unop Neg (Number ntype ntext)]) =
255 processStatement fn (Assign lexps [Number ntype ("-" <> ntext)])
256 processStatement _ (Assign lexp [rexp]) = do
257 parseRightExp (map parseLeftExp lexp) rexp
258 processStatement startupFunctionName (Assign vars exps) | length vars == length exps = do
259 mapM_
260 ( \case
261 (VarName (Name name), expr) -> processStatement startupFunctionName (Assign [VarName (Name (getTempAlias name))] [expr])
262 _ -> error "processStatement: internal error"
263 )
264 $ zip vars exps
265 mapM_ (\(VarName (Name name)) -> addAlias name (getTempAlias name)) vars
266 where
267 getTempAlias name = name <> "&"
268 -- startup function recursive call
269 processStatement fn (FunCall (NormalFunCall (PEVar (VarName (Name fName))) (Args args)))
270 | fn == fName = do
271 LuaAlgBuilder{algStartupArgs} <- get
272 let startupVarsNames = map (fromMaybe (error "processStatement: internal error") . (`HM.lookup` algStartupArgs)) [0 .. (HM.size algStartupArgs)]
273 let startupVarsVersions = map (\x -> LuaValueInstance{lviName = fst x, lviAssignCount = 0, lviIsConstant = False}) startupVarsNames
274 mapM_ parseStartupArg $ zip3 args startupVarsVersions (map (readText . snd) startupVarsNames)
275 where
276 parseStartupArg (arg, valueVersion, index) = do
277 varName <- parseExpArg "loop" arg
278 luaAlgBuilder@LuaAlgBuilder{algGraph} <- get
279 put luaAlgBuilder{algGraph = LuaStatement{fIn = [varName], fOut = [valueVersion], fValues = [index], fName = "loop", fInt = []} : algGraph}
280 processStatement _ (FunCall (NormalFunCall (PEVar (VarName (Name fName))) (Args args))) = do
281 fIn <- mapM (parseExpArg "tmp") args
282 addFunction (fromText fName) fIn [fromString ""]
283 processStatement _fn (FunCall (NormalFunCall (PEVar (SelectName (PEVar (VarName (Name "debug"))) (Name fName))) (Args args))) = do
284 let fIn = map parseTraceArg args
285 luaAlgBuilder@LuaAlgBuilder{algTraceFuncs, algLatestLuaValueInstance} <- get
286 case (fName, fIn) of
287 ("trace", tFmt : vs)
288 | T.isPrefixOf "\"" tFmt && T.isPrefixOf "\"" tFmt -> do
289 let vars = map (\x -> T.pack $ takeWhile (/= '#') $ T.unpack $ getUniqueLuaVariableName (fromMaybe undefined $ HM.lookup x algLatestLuaValueInstance) 0) vs
290 put luaAlgBuilder{algTraceFuncs = (vars, Just $ T.replace "\"" "" tFmt) : algTraceFuncs}
291 ("trace", vs) -> do
292 let vars = map (\x -> T.pack $ takeWhile (/= '#') $ T.unpack $ getUniqueLuaVariableName (fromMaybe undefined $ HM.lookup x algLatestLuaValueInstance) 0) vs
293 put luaAlgBuilder{algTraceFuncs = (vars, Nothing) : algTraceFuncs}
294 _ -> error $ "unknown debug method: " <> show fName <> " " <> show args
295 where
296 parseTraceArg (String s) = s
297 parseTraceArg (PrefixExp (PEVar (VarName (Name name)))) = name
298 parseTraceArg _ = undefined
299 processStatement _ _stat = error $ "unknown statement: " <> show _stat
300
301 addFunction funcName [i] fOut | toString funcName == "buffer" = do
302 addVariable [i] fOut [] "buffer" []
303 addFunction funcName [i] fOut | toString funcName == "brokenBuffer" = do
304 addVariable [i] fOut [] "brokenBuffer" []
305 addFunction funcName [i] _ | toString funcName == "send" = do
306 luaAlgBuilder@LuaAlgBuilder{algGraph} <- get
307 put luaAlgBuilder{algGraph = LuaStatement{fIn = [i], fOut = [], fValues = [], fName = "send", fInt = []} : algGraph}
308 addFunction funcName _ fOut | toString funcName == "receive" = do
309 addVariable [] fOut [] "receive" []
310 addFunction fName _ _ = error $ "unknown function" <> T.unpack fName
311
312 addConstant (Number _valueType valueString) = do
313 luaAlgBuilder@LuaAlgBuilder{algGraph, algVars, algConstants} <- get
314 let lvv = LuaValueInstance{lviName = valueString, lviAssignCount = 0, lviIsConstant = True}
315 case HM.lookup valueString algConstants of
316 Just value -> do
317 let names = fromMaybe (error "lua constants parsing error") $ HM.lookup value algVars
318 let resultName = getUniqueLuaVariableName lvv $ length names
319 put luaAlgBuilder{algVars = HM.insert value (resultName : names) algVars}
320 return resultName
321 Nothing -> do
322 let resultName = getUniqueLuaVariableName lvv 0
323 put
324 luaAlgBuilder
325 { algGraph = LuaStatement{fIn = [], fOut = [lvv], fValues = [readText valueString], fName = "constant", fInt = []} : algGraph
326 , algVars = HM.insert lvv [resultName] algVars
327 , algConstants = HM.insert valueString lvv algConstants
328 }
329 return resultName
330 addConstant _ = undefined
331
332 addVariable fIn fOut fValues fName fInt = do
333 LuaAlgBuilder{algLatestLuaValueInstance} <- get
334 let luaValueInstances = map (\x -> nameToLuaValueInstance algLatestLuaValueInstance x) fOut
335 let func = LuaStatement{fIn, fValues, fName, fInt, fOut = luaValueInstances}
336 mapM_ (uncurry addItemToBuffer) $ zip fOut luaValueInstances
337 mapM_ addItemToVars luaValueInstances
338 luaAlgBuilder@LuaAlgBuilder{algGraph, algConstants, algLatestLuaValueInstance = algLatestLuaValueInstance'} <- get
339 case fName of
340 "constant" -> do
341 case HM.lookup (showText $ head fValues) algConstants of
342 Just lvv -> do
343 put luaAlgBuilder{algLatestLuaValueInstance = HM.insert (head fOut) lvv algLatestLuaValueInstance'}
344 Nothing -> do
345 put luaAlgBuilder{algGraph = func : algGraph, algConstants = HM.insert (showText $ head fValues) (head luaValueInstances) algConstants}
346 _ -> do
347 put luaAlgBuilder{algGraph = func : algGraph}
348 where
349 nameToLuaValueInstance algLatestLuaValueInstance name =
350 case getLuaValueByName name algLatestLuaValueInstance of
351 Just lvv@LuaValueInstance{lviAssignCount} -> lvv{lviAssignCount = lviAssignCount + 1}
352 Nothing -> LuaValueInstance{lviName = name, lviAssignCount = 0, lviIsConstant = False}
353 addItemToBuffer name lvv = do
354 luaAlgBuilder@LuaAlgBuilder{algLatestLuaValueInstance} <- get
355 put luaAlgBuilder{algLatestLuaValueInstance = HM.insert name lvv algLatestLuaValueInstance}
356 addItemToVars name = do
357 luaAlgBuilder@LuaAlgBuilder{algVars} <- get
358 put luaAlgBuilder{algVars = HM.insert name [] algVars}
359
360 addVariableAccess name = do
361 luaAlgBuilder@LuaAlgBuilder{algVars} <- get
362 luaValueInstance <- getLatestLuaValueInstanceByName name
363 case HM.lookup luaValueInstance algVars of
364 Just value -> do
365 let len = length value
366 let resultName = getUniqueLuaVariableName luaValueInstance len
367 put luaAlgBuilder{algVars = HM.insert luaValueInstance (resultName : value) algVars}
368 return resultName
369 Nothing -> error ("variable '" <> show (lviName luaValueInstance) <> " not found. Constants list : " <> show algVars)
370
371 getLatestLuaValueInstanceByName name = do
372 LuaAlgBuilder{algLatestLuaValueInstance} <- get
373 case HM.lookup name algLatestLuaValueInstance of
374 Just value -> return value
375 Nothing -> error $ "variable not found : '" <> show name <> "'."
376
377 addAlias from to = do
378 luaAlgBuilder@LuaAlgBuilder{algLatestLuaValueInstance} <- get
379 case getLuaValueByName to algLatestLuaValueInstance of
380 Just value -> do
381 put luaAlgBuilder{algLatestLuaValueInstance = HM.insert from value algLatestLuaValueInstance}
382 Nothing -> error ("variable '" <> show to <> " not found. Constants list : " <> show algLatestLuaValueInstance)
383
384 getLuaValueByName name buffer = HM.lookup name buffer
385
386 buildAlg syntaxTree =
387 flip execState emptyLuaAlgBuilder $ do
388 let (startupFunctionName, startupFunctionCall, startupFunctionDef) = findStartupFunction syntaxTree
389 statements = funAssignStatements startupFunctionDef
390 _ <- addStartupFuncArgs startupFunctionCall startupFunctionDef
391 mapM_ (processStatement startupFunctionName) statements
392 where
393 emptyLuaAlgBuilder =
394 LuaAlgBuilder
395 { algGraph = []
396 , algLatestLuaValueInstance = HM.empty
397 , algVarCounters = HM.empty
398 , algVars = HM.empty
399 , algStartupArgs = HM.empty
400 , algConstants = HM.empty
401 , algTraceFuncs = []
402 }
403 funAssignStatements (FunAssign _ (FunBody _ _ (Block statements _))) = statements
404 funAssignStatements _ = error "funAssignStatements : not a function assignment"
405
406 findStartupFunction (Block statements Nothing)
407 | [call] <- filter (\case FunCall{} -> True; _ -> False) statements
408 , [funAssign] <- filter (\case FunAssign{} -> True; _ -> False) statements
409 , (FunCall (NormalFunCall (PEVar (VarName (Name fnCall))) _)) <- call
410 , (FunAssign (FunName (Name fnAssign) _ _) _) <- funAssign
411 , fnCall == fnAssign =
412 (fnCall, call, funAssign)
413 findStartupFunction _ = error "can't find startup function in lua source code"
414
415 getLuaBlockFromSources src = either (\e -> error $ "Exception while parsing Lua sources: " <> show e) id $ parseText chunk src
416
417 alg2graph LuaAlgBuilder{algGraph, algLatestLuaValueInstance, algVars} = flip execState (DFCluster []) $ do
418 mapM addToGraph algGraph
419 where
420 addToGraph item = do
421 graph <- get
422 put (addFuncToDataFlowGraph (function2nitta item) graph)
423 return $ fromString ""
424 function2nitta LuaStatement{fName = "buffer", fIn = [i], fOut = [o], fValues = [], fInt = []} = F.buffer (fromText i) $ output o
425 function2nitta LuaStatement{fName = "brokenBuffer", fIn = [i], fOut = [o], fValues = [], fInt = []} = F.brokenBuffer (fromText i) $ output o
426 function2nitta LuaStatement{fName = "constant", fIn = [], fOut = [o], fValues = [x], fInt = []} = F.constant x $ output o
427 function2nitta LuaStatement{fName = "send", fIn = [i], fOut = [], fValues = [], fInt = []} = F.send (fromText i)
428 function2nitta LuaStatement{fName = "add", fIn = [a, b], fOut = [c], fValues = [], fInt = []} = F.add (fromText a) (fromText b) $ output c
429 function2nitta LuaStatement{fName = "sub", fIn = [a, b], fOut = [c], fValues = [], fInt = []} = F.sub (fromText a) (fromText b) $ output c
430 function2nitta LuaStatement{fName = "multiply", fIn = [a, b], fOut = [c], fValues = [], fInt = []} = F.multiply (fromText a) (fromText b) $ output c
431 function2nitta LuaStatement{fName = "divide", fIn = [d, n], fOut = [q], fValues = [], fInt = []} = F.division (fromText d) (fromText n) (output q) []
432 function2nitta LuaStatement{fName = "divide", fIn = [d, n], fOut = [q, r], fValues = [], fInt = []} = F.division (fromText d) (fromText n) (output q) (output r)
433 function2nitta LuaStatement{fName = "neg", fIn = [i], fOut = [o], fValues = [], fInt = []} = F.neg (fromText i) $ output o
434 function2nitta LuaStatement{fName = "receive", fIn = [], fOut = [o], fValues = [], fInt = []} = F.receive $ output o
435 function2nitta LuaStatement{fName = "shiftL", fIn = [a], fOut = [c], fValues = [], fInt = [s]} = F.shiftL s (fromText a) $ output c
436 function2nitta LuaStatement{fName = "shiftR", fIn = [a], fOut = [c], fValues = [], fInt = [s]} = F.shiftR s (fromText a) $ output c
437 function2nitta LuaStatement{fName = "loop", fIn = [a], fOut = [c], fValues = [x], fInt = []} = F.loop x (fromText a) $ output c
438 function2nitta f = error $ "function not found: " <> show f
439 output v =
440 case HM.lookup v algVars of
441 Just names -> map fromText names
442 _ -> error $ "variable not found : " <> show v <> ", buffer : " <> show algLatestLuaValueInstance
443
444 translateLua :: (Var v, Val x) => T.Text -> FrontendResult v x
445 translateLua src =
446 let syntaxTree = getLuaBlockFromSources src
447 luaAlgBuilder = buildAlg syntaxTree
448 frTrace = getFrTrace $ getAllTraceFuncs luaAlgBuilder
449 in FrontendResult{frDataFlow = alg2graph luaAlgBuilder, frTrace, frPrettyLog = prettyLog frTrace}
450 where
451 getAllTraceFuncs algBuilder =
452 let traceFuncs = algTraceFuncs algBuilder
453 startupArgNames =
454 map
455 (\(_idx, (varName, _initValue)) -> varName)
456 $ HM.toList
457 $ algStartupArgs algBuilder
458 in map (\name -> ([name <> "^0"], Nothing)) startupArgNames <> traceFuncs
459
460 getFrTrace traceFuncs = [TraceVar fmt var | (vars, fmt) <- traceFuncs, var <- vars]