summaryrefslogtreecommitdiff
path: root/src/ZNC2.hs
blob: bcd4fb38bfc9267a17008dd8b6a2a46c6233ed1e (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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE UnliftedFFITypes #-}
module ZNC2 where

import Data.Array.Byte
import Data.ByteString (ByteString)
import Data.ByteString.Unsafe qualified as BS
import Data.Text (Text)
import Data.Text.Internal qualified as TI
import Data.Text.Internal.Validate (isValidUtf8ByteArray)
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"
  --                       file buf     length      num events
  c_parse_znc_numevents :: Ptr CChar -> CSize -> IO CSize

foreign import ccall unsafe "tirclogv_parse_znc"
  --             events buffer                  file buf     length
  c_parse_znc :: MutableByteArray# RealWorld -> Ptr CChar -> CSize -> IO ()

foreign import ccall unsafe "tirclogv_fix_utf8_length"
  --                   byte buf      offset   length      length of fixed
  c_fix_utf8_length :: ByteArray# -> CSize -> CSize -> IO CSize

foreign import ccall unsafe "tirclogv_fix_utf8"
  --            output buffer                  byte buf      offset   length
  c_fix_utf8 :: MutableByteArray# RealWorld -> ByteArray# -> CSize -> CSize -> IO ()


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

evRepSz :: Int
evRepSz = 20

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 =
      (HMS (byte 0) (byte 1) (byte 2)
      ,case byte 3 of
         0 -> Join   (textfield 0) (textfield 1)
         1 -> Part   (textfield 0) (textfield 1) (textfield 2)
         2 -> Quit   (textfield 0) (textfield 1) (textfield 2)
         3 -> ReNick (textfield 0) (textfield 1)
         4 -> Talk   (textfield 0) (textfield 1)
         5 -> Notice (textfield 0) (textfield 1)
         6 -> Act    (textfield 0) (textfield 1)
         7 -> Kick   (textfield 0) (textfield 1) (textfield 2)
         8 -> Mode   (textfield 0) (textfield 1)
         9 -> Topic  (textfield 0) (textfield 1)
         _ {- includes 10 -} -> ParseError
      )
      where
        byte :: Int -> Word8
        byte off = indexWord8Array ba# (i * evRepSz + off)

        textfield :: Int -> Text
        textfield n =
          let offset = fromIntegral @Word32 @Int (indexWord8ArrayAsWord32 ba# (i * evRepSz + 4 + 4 * n))
              len    = fromIntegral @Word16 @Int (indexWord8ArrayAsWord16 ba# (i * evRepSz + 16 + 2 * n))
          in if isValidUtf8ByteArray (ByteArray ba#) offset len
               then TI.Text (ByteArray ba#) offset len
               else fixUtf8ByteArray ba# offset len

    indexWord8ArrayAsWord32 :: ByteArray# -> Int -> Word32
    indexWord8ArrayAsWord32 ba# (I# i#) = W32# (indexWord8ArrayAsWord32# ba# i#)

    indexWord8ArrayAsWord16 :: ByteArray# -> Int -> Word16
    indexWord8ArrayAsWord16 ba# (I# i#) = W16# (indexWord8ArrayAsWord16# ba# i#)

    indexWord8Array :: ByteArray# -> Int -> Word8
    indexWord8Array ba# (I# i#) = W8# (indexWord8Array# ba# i#)

{-# 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# #)

-- | Returns an unpinned byte array
{-# NOINLINE fixUtf8ByteArray #-}
fixUtf8ByteArray :: ByteArray# -> Int -> Int -> Text
fixUtf8ByteArray input# offset len = unsafePerformIO $ do
  let offCS = fromIntegral @Int @CSize offset
      lenCS = fromIntegral @Int @CSize len
  outlenCS <- c_fix_utf8_length input# offCS lenCS

  let !outlen@(I# outlen#) = fromIntegral @CSize @Int outlenCS
  MutableByteArray dst# <-
    IO $ \s -> case newByteArray# outlen# s of
                 (# s', mba# #) -> (# s', MutableByteArray mba# #)
  c_fix_utf8 dst# input# offCS lenCS
  IO $ \s -> case unsafeFreezeByteArray# dst# s of
               (# s', ba# #) -> (# s', TI.Text (ByteArray ba#) 0 outlen #)