packages feed

json-query-0.2.3.0: src/Json/Parser.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}

module Json.Parser
  ( Parser (..)
  , MemberParser (..)

    -- * Run
  , run

    -- * Object Parsing
  , key
  , keyOptNull
  , members

    -- * Arrays
  , smallArray
  , foldSmallArray
  , traverseMembers

    -- * Specific Data Constructors
  , object
  , array
  , number
  , boolean
  , string

    -- * Trivial Combinators
  , int
  , int32
  , word16
  , word32
  , word64

    -- * Failing
  , fail

    -- * Modified Context
  , contextually
  ) where

import Prelude hiding (fail)

import Control.Applicative (Alternative (..))
import Control.Monad.ST (runST)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Except (ExceptT (ExceptT), runExceptT)
import Data.Foldable (foldlM)
import Data.Int (Int32)
import Data.List (find)
import Data.Number.Scientific (Scientific)
import Data.Primitive (SmallArray)
import Data.Text.Short (ShortText)
import Data.Word (Word16, Word32, Word64)
import Json (Member (Member), Value (Array, Number, Object))

-- import Json.Path (Path(Nil,Key,Index))
import Json.Context (Context (Index, Key, Top))
import Json.Error (Error (..))
import Json.Errors (Errors)

import qualified Data.Number.Scientific as SCI
import qualified Data.Primitive as PM
import qualified Json
import qualified Json.Errors as Errors

newtype Parser a = Parser
  {runParser :: Context -> Either Errors a}
  deriving stock (Functor)

instance Applicative Parser where
  pure a = Parser (\_ -> Right a)
  Parser f <*> Parser g = Parser $ \p -> do
    h <- f p
    y <- g p
    pure (h y)

instance Alternative Parser where
  empty = fail mempty
  f <|> g = Parser $ \p -> case runParser f p of
    Right x -> Right x
    Left aErrs -> case runParser g p of
      Right y -> Right y
      Left bErrs -> Left (aErrs <> bErrs)

instance Monad Parser where
  Parser f >>= g = Parser $ \p -> do
    x <- f p
    runParser (g x) p

newtype MemberParser a = MemberParser
  {runMemberParser :: Context -> SmallArray Member -> Either Errors a}
  deriving stock (Functor)

instance Applicative MemberParser where
  pure a = MemberParser (\_ _ -> Right a)
  MemberParser f <*> MemberParser g = MemberParser $ \p mbrs -> do
    h <- f p mbrs
    y <- g p mbrs
    pure (h y)

instance Alternative MemberParser where
  empty = MemberParser $ \p _ -> Left (Errors.singleton Error {message = mempty, context = p})
  a <|> b = MemberParser $ \p mbrs ->
    case runMemberParser a p mbrs of
      Right x -> Right x
      Left aErrs -> case runMemberParser b p mbrs of
        Right y -> Right y
        Left bErrs -> Left (aErrs <> bErrs)

instance Monad MemberParser where
  parser >>= k = MemberParser $ \p mbrs ->
    case runMemberParser parser p mbrs of
      Left p' -> Left p'
      Right x -> runMemberParser (k x) p mbrs

run :: Parser a -> Either Errors a
run (Parser f) = case f Top of
  Right a -> Right a
  Left e -> Left e

fail :: ShortText -> Parser a
fail !msg = Parser (\e -> Left (Errors.singleton Error {context = e, message = msg}))

object :: Value -> Parser (SmallArray Member)
object = \case
  Object xs -> pure xs
  _ -> fail "expected object"

array :: Value -> Parser (SmallArray Value)
array = \case
  Array xs -> pure xs
  _ -> fail "expected array"

members :: MemberParser a -> SmallArray Member -> Parser a
members (MemberParser f) mbrs = Parser (\p -> f p mbrs)

number :: Value -> Parser Scientific
number = \case
  Number n -> pure n
  _ -> fail "expected number"

string :: Value -> Parser ShortText
string = \case
  Json.String n -> pure n
  _ -> fail "expected string"

int :: Scientific -> Parser Int
int m = case SCI.toInt m of
  Just n -> pure n
  _ -> fail "expected number in signed machine integer range"

int32 :: Scientific -> Parser Int32
int32 m = case SCI.toInt32 m of
  Just n -> pure n
  _ -> fail "expected number in range [-2^31,2^31-1)"

word16 :: Scientific -> Parser Word16
word16 m = case SCI.toWord16 m of
  Just n -> pure n
  _ -> fail "expected number in range [0,2^16)"

word32 :: Scientific -> Parser Word32
word32 m = case SCI.toWord32 m of
  Just n -> pure n
  _ -> fail "expected number in range [0,2^32)"

word64 :: Scientific -> Parser Word64
word64 m = case SCI.toWord64 m of
  Just n -> pure n
  _ -> fail "expected number in range [0,2^64)"

boolean :: Value -> Parser Bool
boolean = \case
  Json.True -> pure True
  Json.False -> pure False
  _ -> fail "expected boolean"

-- members :: Parser Value (Chunks Member)
-- members = _

key :: ShortText -> (Value -> Parser a) -> MemberParser a
key !name f = MemberParser $ \p mbrs ->
  let !p' = Key name p
   in case find (\Member {key = k} -> k == name) mbrs of
        Nothing -> Left (Errors.singleton (Error {context = p', message = "key not found: " <> name}))
        Just Member {value} -> runParser (f value) p'

{- | Variant of 'key' that supplies the JSON value @null@ to the
callback if the key is not found. Using this parser combinators implies
that there is no distinction between @null@ and an absent value in
the encoding scheme.
-}
keyOptNull :: ShortText -> (Value -> Parser a) -> MemberParser a
keyOptNull !name f = MemberParser $ \p mbrs ->
  let !p' = Key name p
      val = case find (\Member {key = k} -> k == name) mbrs of
        Nothing -> Json.Null
        Just Member {value} -> value
   in runParser (f val) p'

-- object2 ::
--      (a -> b -> c)
--   -> ShortText -> Parser a
--   -> ShortText -> Parser b
--   -> Parser c
-- object2 f ka pa kb pb = Parser $ \p v -> case v

-- elements :: Parser Value (Chunks Value)
-- elements = _

{- | Run the same parser against every element in a 'SmallArray'. This adjusts
the context at each element.
-}
smallArray :: (Value -> Parser a) -> SmallArray Value -> Parser (SmallArray a)
smallArray f xs = Parser $ \ !p -> runST do
  let !len = length xs
  dst <- PM.newSmallArray len errorThunk
  runExceptT $ do
    _ <-
      foldlM
        ( \ix x -> do
            !y <- ExceptT (pure (runParser (f x) (Index ix p)))
            lift (PM.writeSmallArray dst ix y)
            pure (ix + 1)
        )
        0
        xs
    lift (PM.unsafeFreezeSmallArray dst)

{- | Run the parser against every element in a 'SmallArray', updating an
accumulator at each step. Folds from left to right. This adjusts
the context at each element. Typically, type @a@ is @Value@.
-}
foldSmallArray :: (b -> a -> Parser b) -> b -> SmallArray a -> Parser b
{-# INLINE foldSmallArray #-}
foldSmallArray f acc0 xs = Parser $ \ !p -> runST do
  -- TODO: make sure tuples get elided
  runExceptT $ do
    (!_, r) <-
      foldlM
        ( \(!ix, !acc) x -> do
            !acc' <- ExceptT (pure (runParser (f acc x) (Index ix p)))
            pure (ix + 1, acc')
        )
        (0, acc0)
        xs
    pure r

-- | Traverse the members. The adjusts the context at each member.
traverseMembers :: (Member -> Parser a) -> SmallArray Member -> Parser (SmallArray a)
traverseMembers f !xs = Parser $ \ !p -> runST do
  let !len = length xs
  dst <- PM.newSmallArray len errorThunk
  runExceptT $ do
    !_ <-
      foldlM
        ( \ !ix x@Member {key = k} -> do
            !y <- ExceptT (pure (runParser (f x) (Key k p)))
            lift (PM.writeSmallArray dst ix y)
            pure (ix + 1)
        )
        0
        xs
    lift (PM.unsafeFreezeSmallArray dst)

errorThunk :: a
{-# NOINLINE errorThunk #-}
errorThunk = errorWithoutStackTrace "Json.Parser: implementation mistake"

-- | Run a parser in a modified context.
contextually :: (Context -> Context) -> Parser a -> Parser a
{-# INLINE contextually #-}
contextually f (Parser g) =
  Parser
    ( \p ->
        let !p' = f p
         in g p'
    )