cpmonad-0.1.0.0: lib/Cpmonad/Printer.hs
{-# OPTIONS_GHC -Wno-unused-imports #-}
-- | Combinators for defining a parser and a serializer with a single definition
module Cpmonad.Printer (
Printer (..),
char,
sp,
endl,
pint,
pvec,
pvecN,
pvecint,
pvecintN,
pvecvecint,
nest,
len,
) where
import Control.Monad.ST (runST)
import Cpmonad.Misc
import Data.ByteString.Builder qualified as B
import Data.ByteString.Char8 (ByteString)
import Data.ByteString.Char8 qualified as B
import Data.Default
import Data.Vector (Vector, (!))
import Data.Vector qualified as V
import Data.Vector.Mutable qualified as VM
import Lens.Micro
{- | A printer is both a parser and a serializer.
Parsing is done in an "FSM" fashion. Each parser gets the current state and
updates it with the new information. The state is initialized to 'def'.
To combine printers, the printers must operate on the same state type @a@.
Printers take lens to the part of the object to operate on. See the examples.
All of the builtin printers ignore extra whitespace.
__Important__: the printers here, on their own, are valid — parser is
an inverse of the serializer. But when combined, they can still become ambiguous.
To prevent that, you must use whitespace when combining parsers. If I were to
make 'pint' output space, it would lead to trailing spaces in lines. Therefore,
you need to choose between sp and endl explicit each time. 'Cpmonad.generateTests'
will check for this and will complain if your printer is invalid.
The following characters are considered whitespace: @" \\r\\n"@
==== __Examples:__
Full-blown example:
@
data Input = Input {_k :: Int, _arr :: Vector Int, _queries :: Vector (Int, Int)}
makeLenses ''Input
printer = pint (arr . len) <> sp <> pint k <> endl
<> pvecint sp arr <> endl
<> pint (queries . len)
<> pvec endl queries (pint _1 <> sp <> pint _2) <> endl
@
Example input:
@
4 25
1 1 2 1
3
0 1
0 2
1 3
@
Parsed object:
@
Input1
{ _k = 25
, _arr = [1,1,2,1]
, _queries = [(0,1),(0,2),(1,3)]
}
@
Printer for a single integers:
>>> pint id :: Printer Int
Example input:
@
42
@
Printer for a pair of integers
>>> pint _1 <> sp <> pint _2 :: Printer (Int, Int)
Example input:
@
42 37
@
Printer for an array of integers:
>>> pint len <> pvecint sp id :: Printer (Vector Int)
Example input:
@
5
1 2 3 40 500
@
Printer for an array of integers with explicit field containing their count:
>>> pint _1 <> pvecN sp _1 _2 (pint id) :: Printer (Int, Vector Int)
Example input: the same as above
-}
data Printer a = Printer
{ toPrinted :: a -> Maybe B.Builder
, fromPrinted :: (a, ByteString) -> Maybe (a, ByteString)
}
instance Semigroup (Printer a) where
p1 <> p2 = Printer{..}
where
toPrinted xb = p1.toPrinted xb <> p2.toPrinted xb
fromPrinted xs = p1.fromPrinted xs >>= p2.fromPrinted
skipSpace :: ByteString -> ByteString
skipSpace = B.dropWhile (\c -> c == ' ' || c == '\n' || c == '\r') -- idc about other ascii ws chars
-- | Outputs the character, and consumes character after skipping whitespace. /Don't use this for whitespace/
char :: Char -> Printer a
char c = Printer{..}
where
toPrinted !_ = Just $ B.char8 c
fromPrinted (!x, !s) = do
c' <- skipSpace s `B.indexMaybe` 0
if c' == c then Just (x, B.tail s) else Nothing
-- | Outputs a single space, and consumes all whitespace. Doesn't change the state.
sp :: Printer a
sp = Printer{..}
where
toPrinted !_ = Just $ B.char8 ' '
fromPrinted (!x, !s) = Just (x, skipSpace s)
-- | Outputs a newline (LF), and consumes all whitespace. Doesn't change the state.
endl :: Printer a
endl = Printer{..}
where
toPrinted !_ = Just $ B.char8 '\n'
fromPrinted (!x, !s) = Just (x, skipSpace s)
-- | Focuses the printer on part of the state. See the implementation if confused.
nest :: Printer b -> Lens' a b -> Printer a
nest p f = Printer{..}
where
toPrinted !a = p.toPrinted (a ^. f)
fromPrinted (!a, !s) =
let x = p.fromPrinted (a ^. f, s)
in case x of
Just (b, c) -> Just (a & f .~ b, c)
Nothing -> Nothing
-- extremely scuffed but trust me i couldn't find a better way
pint :: Lens' a Int -> Printer a
pint = nest Printer{..}
where
toPrinted !x = Just $ B.intDec x
fromPrinted (!_, !s) = do
(n, s') <- B.readInt (skipSpace s)
pure (n, s')
pvec
:: (Default b)
=> (forall c. Printer c)
-- ^ separator (e.g. 'sp')
-> Lens' a (Vector b)
-> Printer b
-> Printer a
pvec sep arr p = pvecN sep (arr . len) arr p
pvecN
:: (Default b)
=> (forall c. Printer c)
-- ^ separator
-> SimpleGetter a Int
-- ^ number of elements
-> Lens' a (Vector b)
-- ^ the vector itself
-> Printer b
-> Printer a
pvecN sep n arr p = Printer{..}
where
toPrinted !x
| x ^. n /= V.length (x ^. arr) = Nothing
| x ^. n == 0 = Just mempty
| otherwise =
p.toPrinted (V.head (x ^. arr))
<> mconcat [sep.toPrinted () <> p.toPrinted el | el <- V.toList . V.tail $ x ^. arr]
fromPrinted (!x, !s') =
let count = x ^. n
in if count == 0
then Just (x & arr .~ V.empty, s')
else runST do
v <- VM.new count
let go !s !i
| i == count = pure (Just s)
| otherwise =
case sep.fromPrinted ((), s) of
Nothing -> pure Nothing
Just (_, s2) ->
case p.fromPrinted (def, s2) of
Nothing -> pure Nothing
Just (val, s3) -> VM.write v i val >> go s3 (i + 1)
res <- go s' 0
v' <- V.freeze v
pure $ (x & arr .~ v',) <$> res
pvecint :: (forall b. Printer b) -> Lens' a (V.Vector Int) -> Printer a
pvecint sep arr = pvec sep arr (pint id)
pvecintN :: (forall b. Printer b) -> SimpleGetter a Int -> Lens' a (V.Vector Int) -> Printer a
pvecintN sep n arr = pvecN sep n arr (pint id)
{- | This is a special parser for a matrix of integers. Each row is separated by newlines and
each number in the row is separated by spaces.
-}
pvecvecint :: SimpleGetter a Int -> SimpleGetter a Int -> Lens' a (Vector (Vector Int)) -> Printer a
pvecvecint n m arr = Printer{..}
where
toPrinted !x
| x ^. n /= V.length (x ^. arr) = Nothing
| otherwise =
let vec i = (pvecintN sp _1 _2).toPrinted (x ^. m, (x ^. arr) ! i)
in mconcat [vec i <> Just (B.char8 '\n') | i <- [0 .. x ^. n - 1]]
fromPrinted (!x, !s') =
let vec s i = (pvecintN sp _1 _2).fromPrinted ((x ^. m, (x ^. arr) ! i), s)
in runST do
let count = x ^. n
v <- VM.new count
let go !s !i
| i == count = pure (Just s)
| otherwise =
case vec s i of
Nothing -> pure Nothing
Just ((_, vv), s'') -> VM.write v i vv >> go s'' (i + 1)
res <- go s' 0
v' <- V.freeze v
pure $ (x & arr .~ v',) <$> res
-- | Iso between Vector and its length. When set, it fills the vector with n @def@\'s.
len :: (Default a) => Lens' (Vector a) Int
len = lens V.length (\_ n -> V.replicate n def)