summaryrefslogtreecommitdiff
path: root/src/ZNC2.hs
blob: 7a13f52c30dc45d2e7a76597ffae3846c7736fbc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE UnliftedFFITypes #-}
module ZNC2 where

import Data.Array.Byte
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.ByteString.Short qualified as BSS
import Data.ByteString.Unsafe qualified as BS
import Data.Text (Text)
import Data.Text qualified as T
import Data.Text.Encoding qualified as TE
import Foreign.C.Types
import Foreign.Ptr
import GHC.Exts
import GHC.IO (IO(IO))
import GHC.Word
import System.IO.Unsafe (unsafePerformIO)

import Util
import ZNC (Event(..))


foreign import ccall unsafe "tirclogv_parse_znc_numevents"
  c_parse_znc_numevents :: Ptr CChar -> CSize -> IO CSize

foreign import ccall unsafe "tirclogv_parse_znc"
  c_parse_znc :: MutableByteArray# RealWorld -> Ptr CChar -> CSize -> IO ()


-- For each event: (total 28 bytes)
-- * 1 byte hour
-- * 1 byte minute
-- * 1 byte second
-- * 1 byte event kind
-- * 4 bytes text pointer 1
-- * 4 bytes text length 1
-- * 4 bytes text pointer 2
-- * 4 bytes text length 2
-- * 4 bytes text pointer 3
-- * 4 bytes text length 3
data Events = Events ByteArray#

evRepSz :: Int
evRepSz = 28

parseLog :: ByteString -> [(HMS, Event)]
parseLog bs =
  let !(Events ba#) = parseLogToEvents bs
      nev = I# (sizeofByteArray# ba#) `quot` evRepSz
  in [deserialise ba# i | i <- [0 .. nev-1]]
  where
    deserialise :: ByteArray# -> Int -> (HMS, Event)
    deserialise ba# (I# i#) =
      (HMS (byte 0) (byte 1) (byte 2)
      ,case byte 3 of
         0 -> Join (textfield 0) (textfield 1))
      where
        byte :: Int -> Word8
        byte (I# off#) = W8# (indexWord8Array# ba# (i# +# off#))

        textfield :: Int -> Text
        textfield (I# n#) =
          let offset = W32# (indexWord32Array# ba# (i# +# 1# +# (2# *# n#)))
              len    = W32# (indexWord32Array# ba# (i# +# 2# +# (2# *# n#)))
              -- slice = BS.unsafePackCStringLen  -- safe because it's pinned
              --           (Ptr (byteArrayContents# ba#) `plusPtr` fromIntegral @Word32 @Int offset
              --           ,fromIntegral @Word32 @Int len)
          in if BSS.isValidUtf8 (BSS.ShortByteString (ByteArray ba#))
                 then _ else _

{-# NOINLINE parseLogToEvents #-}
parseLogToEvents :: ByteString -> Events
parseLogToEvents bs = unsafePerformIO $
  BS.unsafeUseAsCStringLen bs $ \(bsptr, bslen) -> do
    let bslenCS = fromIntegral @Int @CSize bslen
    numev <- c_parse_znc_numevents bsptr bslenCS
    let !(I# numbytes#) = fromIntegral @CSize @Int numev * evRepSz

    MutableByteArray dst# <-
      IO $ \s -> case newPinnedByteArray# numbytes# s of
                   (# s', mba# #) -> (# s', MutableByteArray mba# #)
    c_parse_znc dst# bsptr bslenCS
    IO $ \s -> case unsafeFreezeByteArray# dst# s of
                 (# s', ba# #) -> (# s', Events ba# #)