packages feed

hq-json-0.1.1: src/HQ/JSON/Event.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE NoImplicitPrelude #-}

module HQ.JSON.Event where

import Data.Aeson (Value (..))
import Data.Aeson.Key qualified as Key
import Data.Aeson.KeyMap qualified as KeyMap
import Data.Scientific (Scientific)
import Data.Vector qualified as Vector
import HQ.Runner.Error (RunnerError (..))
import Relude hiding (Compose, id, many, some, state)

--------------------------------------------------------------------------------
-- Events
--------------------------------------------------------------------------------

data JSONEvent
  = JSONBeginObject
  | JSONEndObject
  | JSONBeginArray
  | JSONEndArray
  | JSONObjectKey Text
  | JSONNull
  | JSONBool Bool
  | JSONNumber Scientific
  | JSONString Text
  deriving stock (Eq, Show)

--------------------------------------------------------------------------------
-- Event classifiers
--------------------------------------------------------------------------------

-- | Matching close for a container open; 'Nothing' for non-opens.
-- Single table for the open/close dispatch in Take/Cursor.
matchingClose :: JSONEvent -> Maybe JSONEvent
matchingClose = \case
  JSONBeginArray -> Just JSONEndArray
  JSONBeginObject -> Just JSONEndObject
  _ -> Nothing

-- | Parse exactly one value from events; trailing events are rejected.
eventsToValue :: [JSONEvent] -> Either RunnerError Value
eventsToValue events = case parseValue events of
  Left err -> Left err
  Right (value, []) -> Right value
  Right _ -> Left TrailingEventsAfterValue
  where
    parseValue :: [JSONEvent] -> Either RunnerError (Value, [JSONEvent])
    parseValue (JSONNull : rest) = Right (Null, rest)
    parseValue (JSONBool b : rest) = Right (Bool b, rest)
    parseValue (JSONNumber n : rest) = Right (Number n, rest)
    parseValue (JSONString s : rest) = Right (String s, rest)
    parseValue (JSONBeginArray : rest) =
      first (Array . Vector.fromList) <$> parseElements rest
    parseValue (JSONBeginObject : rest) =
      first (Object . KeyMap.fromList) <$> parseMembers rest
    parseValue _ = Left ExpectedJSONValue

    parseElements :: [JSONEvent] -> Either RunnerError ([Value], [JSONEvent])
    parseElements = parseDelimited JSONEndArray parseValue

    withKey k v = (Key.fromText k, v)
    parseMembers :: [JSONEvent] -> Either RunnerError ([(Key.Key, Value)], [JSONEvent])
    parseMembers = parseDelimited JSONEndObject $ \case
      (JSONObjectKey key : rest) -> first (withKey key) <$> parseValue rest
      _ -> Left ExpectedObjectKeyEvent

-- | Parse a delimited list ending in @end@; @parseOne@ parses a
-- single element (failing on anything else). Shared by 'parseElements'
-- and 'parseMembers', which differ only in end token and element shape.
-- Inlined so @parseOne@ specializes per list kind.
{-# INLINE parseDelimited #-}
parseDelimited ::
  JSONEvent ->
  ([JSONEvent] -> Either RunnerError (a, [JSONEvent])) ->
  [JSONEvent] ->
  Either RunnerError ([a], [JSONEvent])
parseDelimited end parseOne = go
  where
    go (ev : rest) | ev == end = Right ([], rest)
    go input = do
      (x, rest) <- parseOne input
      (xs, rest') <- go rest
      Right (x : xs, rest')

-- | Encode a JSON value as a sequence of events.
valueToEvents :: Value -> [JSONEvent]
valueToEvents Null = [JSONNull]
valueToEvents (Bool b) = [JSONBool b]
valueToEvents (Number n) = [JSONNumber n]
valueToEvents (String s) = [JSONString s]
valueToEvents (Array values) =
  JSONBeginArray : (concatMap valueToEvents (Vector.toList values) <> [JSONEndArray])
valueToEvents (Object members) =
  JSONBeginObject : (concatMap member (KeyMap.toList members) <> [JSONEndObject])
  where
    member (key, value) = JSONObjectKey (Key.toText key) : valueToEvents value