Skip to content

Commit 95c9491

Browse files
authored
Merge pull request #848 from smucclaw/mengwong/cli-batch-json
2 parents cd6852e + e7bae57 commit 95c9491

File tree

4 files changed

+399
-3
lines changed

4 files changed

+399
-3
lines changed

jl4-core/src/L4/Evaluate/ValueLazy.hs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ module L4.Evaluate.ValueLazy where
22

33
import Base
44
import Control.Concurrent (ThreadId)
5+
import Data.Aeson (ToJSON(..), object, (.=))
6+
import qualified Data.Aeson as Aeson
7+
import qualified Data.Aeson.Key as Key
8+
import Data.Ratio (numerator, denominator)
9+
import qualified Data.Vector as Vector
510
import Data.Time (Day, UTCTime)
611
import Data.Time.LocalTime (TimeOfDay)
712
import L4.Syntax
@@ -242,3 +247,72 @@ instance NFData TernaryBuiltinFun where
242247
rnf TernaryAlwaysBetween = ()
243248
rnf TernaryTimeFromHMS = ()
244249
rnf TernaryDatetimeFromDTZ = ()
250+
251+
-- ----------------------------------------------------------------------------
252+
-- ToJSON instances for batch --json output
253+
-- ----------------------------------------------------------------------------
254+
255+
-- | Get the constructor name as Text from a Resolved name.
256+
resolvedNameText :: Resolved -> Text
257+
resolvedNameText = rawNameToText . rawName . getOriginal
258+
259+
-- | Flatten a ValCons/ValNil chain into a JSON array.
260+
-- If the structure is not a proper list, fall back to a two-element array.
261+
flattenList :: ToJSON a => a -> a -> Aeson.Value
262+
flattenList x xs = case toJSON xs of
263+
Aeson.Array arr -> Aeson.Array (Vector.cons (toJSON x) arr)
264+
_ -> toJSON [toJSON x, toJSON xs]
265+
266+
instance ToJSON NF where
267+
toJSON (MkNF val) = toJSON val
268+
toJSON Omitted = Aeson.Null
269+
270+
instance ToJSON a => ToJSON (Value a) where
271+
toJSON (ValNumber r)
272+
| denominator r == 1 = toJSON (numerator r)
273+
| otherwise = toJSON (fromRational r :: Double)
274+
toJSON (ValString s) = toJSON s
275+
toJSON (ValDate d) = toJSON (show d)
276+
toJSON (ValTime t) = toJSON (show t)
277+
toJSON (ValDateTime utc tz) = object ["utc" .= utc, "timezone" .= tz]
278+
toJSON ValNil = toJSON ([] :: [Aeson.Value])
279+
toJSON (ValCons x xs) = flattenList x xs
280+
toJSON (ValConstructor name [])
281+
| cname == "NOTHING" = Aeson.Null
282+
| cname == "EMPTY" = toJSON ([] :: [Aeson.Value])
283+
| cname == "TRUE" = Aeson.Bool True
284+
| cname == "FALSE" = Aeson.Bool False
285+
| otherwise = toJSON cname
286+
where cname = resolvedNameText name
287+
toJSON (ValConstructor name [v])
288+
| resolvedNameText name == "JUST" = toJSON v
289+
toJSON (ValConstructor name fields) = object
290+
[ Key.fromText (resolvedNameText name) .= toJSON fields ]
291+
toJSON (ValClosure{}) = toJSON ("<function>" :: Text)
292+
toJSON (ValObligation{}) = toJSON ("<obligation>" :: Text)
293+
toJSON (ValROp{}) = toJSON ("<deferred-op>" :: Text)
294+
toJSON (ValNullaryBuiltinFun{}) = toJSON ("<builtin>" :: Text)
295+
toJSON (ValUnaryBuiltinFun{}) = toJSON ("<builtin>" :: Text)
296+
toJSON (ValBinaryBuiltinFun{}) = toJSON ("<builtin>" :: Text)
297+
toJSON (ValTernaryBuiltinFun{}) = toJSON ("<builtin>" :: Text)
298+
toJSON (ValPartialTernary{}) = toJSON ("<partial>" :: Text)
299+
toJSON (ValPartialTernary2{}) = toJSON ("<partial>" :: Text)
300+
toJSON (ValUnappliedConstructor r) = toJSON (resolvedNameText r)
301+
toJSON (ValAssumed r) = toJSON ("<assumed:" <> resolvedNameText r <> ">" :: Text)
302+
toJSON (ValEnvironment{}) = toJSON ("<environment>" :: Text)
303+
toJSON (ValBreached reason) = object ["breached" .= toJSON reason]
304+
305+
instance ToJSON a => ToJSON (ReasonForBreach a) where
306+
toJSON (DeadlineMissed deadline now elapsed _party action limit) = object
307+
[ "type" .= ("deadline_missed" :: Text)
308+
, "deadline" .= deadline
309+
, "now" .= now
310+
, "elapsed" .= (fromRational elapsed :: Double)
311+
, "action" .= show action
312+
, "limit" .= (fromRational limit :: Double)
313+
]
314+
toJSON (ExplicitBreach mParty mReason) = object
315+
[ "type" .= ("explicit_breach" :: Text)
316+
, "party" .= mParty
317+
, "reason" .= mReason
318+
]

jl4-core/src/L4/EvaluateLazy.hs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,26 @@ prettyEvalDirectiveResultWithFields fields (MkEvalDirectiveResult _range res mtr
374374
Nothing -> Text.empty
375375
Just t -> "\n─────\n" <> prettyLayout t
376376

377+
-- ----------------------------------------------------------------------------
378+
-- ToJSON instances for batch --json output
379+
-- ----------------------------------------------------------------------------
380+
381+
instance Aeson.ToJSON EvalDirectiveResult where
382+
toJSON (MkEvalDirectiveResult _range res _trace) = Aeson.object
383+
[ "result" Aeson..= res
384+
, "trace" Aeson..= Aeson.Null
385+
]
386+
387+
instance Aeson.ToJSON EvalDirectiveValue where
388+
toJSON (Assertion b) = Aeson.object
389+
[ "type" Aeson..= ("assertion" :: Text)
390+
, "value" Aeson..= b
391+
]
392+
toJSON (Reduction (Right val)) = Aeson.toJSON val
393+
toJSON (Reduction (Left exc)) = Aeson.object
394+
[ "error" Aeson..= Text.unlines (prettyEvalException exc)
395+
]
396+
377397
prettyEvalDirectiveValueWithFields :: ConstructorFieldNames -> EvalDirectiveValue -> Text
378398
prettyEvalDirectiveValueWithFields _fields (Assertion True) = "assertion satisfied"
379399
prettyEvalDirectiveValueWithFields _fields (Assertion False) = "assertion failed"

jl4/app/Main.hs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,10 +365,13 @@ main = do
365365
pure mEval
366366

367367
let (status, output, diagnostics) = case mEvalRes of
368-
Nothing ->
368+
Nothing ->
369369
("error" :: Text, Aeson.Null, Aeson.toJSON evalErrs)
370-
Just evalResults ->
371-
("success", Aeson.toJSON (map (Text.pack . show) evalResults), Aeson.Array mempty)
370+
Just evalResults
371+
| options.batchJson ->
372+
("success", Aeson.toJSON evalResults, Aeson.Array mempty)
373+
| otherwise ->
374+
("success", Aeson.toJSON (map (Text.pack . show) evalResults), Aeson.Array mempty)
372375

373376
pure $ Aeson.object
374377
[ "input" Aeson..= input
@@ -537,6 +540,7 @@ data Options = MkOptions
537540
, batchFormat :: Maybe Text
538541
, entrypoint :: Maybe Text
539542
, stateGraph :: Bool -- Output state transition graph from regulative rules
543+
, batchJson :: Bool -- Output batch results as clean JSON instead of Show
540544
}
541545

542546
optionsDescription :: Options.Parser Options
@@ -556,6 +560,7 @@ optionsDescription = MkOptions
556560
<*> optional (Options.strOption (long "format" <> short 'f' <> metavar "FORMAT" <> help "Input/output format (json|yaml|csv); required when reading from stdin"))
557561
<*> optional (Options.strOption (long "entrypoint" <> short 'e' <> metavar "FUNCTION" <> help "Name of @export function to call (defaults to @export default or first @export)"))
558562
<*> switch (long "state-graph" <> help "Output state transition graph from regulative rules as GraphViz DOT")
563+
<*> switch (long "json" <> help "Output batch results as clean JSON values instead of Haskell Show representation")
559564

560565
fixedNowReader :: ReadM UTCTime
561566
fixedNowReader =

0 commit comments

Comments
 (0)