File: Parser.hs

package info (click to toggle)
haskell-irc 0.6.1.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 96 kB
  • sloc: haskell: 464; makefile: 4
file content (152 lines) | stat: -rw-r--r-- 4,458 bytes parent folder | download
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
-- | Parsec parsers and a general parsing interface for IRC messages
module Network.IRC.Parser (
    -- * Parsing and Formatting Functions
    decode -- :: String -> Maybe Message

    -- * Parsec Combinators for Parsing IRC messages
  , prefix         -- :: Parser Prefix
  , serverPrefix   -- :: Parser Prefix
  , nicknamePrefix -- :: Parser Prefix
  , command        -- :: Parser Command
  , parameter      -- :: Parser Parameter
  , message        -- :: Parser Message
  , crlf           -- :: Parser ()
  , spaces         -- :: Parser ()

    -- * Deprecated Functions
  , parseMessage
  ) where

import Network.IRC.Base

import Data.Char
import Data.Word
import Data.ByteString hiding (elem, map, empty)

import Control.Monad (void)
import Control.Applicative
import Data.Attoparsec.ByteString

-- | Casts a character (assumed to be ASCII) to its corresponding byte.
asciiToWord8 :: Char -> Word8
asciiToWord8 = fromIntegral . ord

wSpace :: Word8
wSpace = asciiToWord8 ' '

wTab :: Word8
wTab = asciiToWord8 '\t'

wBell :: Word8
wBell = asciiToWord8 '\b'

wDot :: Word8
wDot = asciiToWord8 '.'

wExcl :: Word8
wExcl = asciiToWord8 '!'

wAt :: Word8
wAt = asciiToWord8 '@'

wCR :: Word8
wCR = asciiToWord8 '\r'

wLF :: Word8
wLF = asciiToWord8 '\n'

wColon :: Word8
wColon = asciiToWord8 ':'

-- | Parse a String into a Message.
decode :: ByteString    -- ^ Message string
       -> Maybe Message -- ^ Parsed message
decode str = case parseOnly message str of
  Left _ -> Nothing
  Right r -> Just r

-- | The deprecated version of decode
parseMessage :: ByteString -> Maybe Message
parseMessage  = decode

-- | Convert a parser that consumes all space after it
tokenize  :: Parser a -> Parser a
tokenize p = p <* spaces

-- | Consume only spaces, tabs, or the bell character
spaces :: Parser ()
spaces  = skip (\w -> w == wSpace ||
                      w == wTab ||
                      w == wBell)

-- | Parse a Prefix
prefix :: Parser Prefix
prefix  = word8 wColon *> (try nicknamePrefix <|> serverPrefix)
          <?> "prefix"

-- | Parse a Server prefix
serverPrefix :: Parser Prefix
serverPrefix  = Server <$> takeTill (== wSpace)
                <?> "serverPrefix"

-- | optionMaybe p tries to apply parser p. If p fails without consuming input,
-- | it return Nothing, otherwise it returns Just the value returned by p.
optionMaybe :: Parser a -> Parser (Maybe a)
optionMaybe p = option Nothing (Just <$> p)

-- | Parse a NickName prefix
nicknamePrefix :: Parser Prefix
nicknamePrefix  = do
  n <- takeTill (inClass " .!@\r\n")
  p <- peekWord8
  case p of
    Just c | c == wDot -> empty
    _                  -> NickName n <$>
                                optionMaybe (word8 wExcl *> takeTill (\w -> w == wSpace ||
                                                                            w == wAt ||
                                                                            w == wCR ||
                                                                            w == wLF))
                            <*> optionMaybe (word8 wAt *> takeTill (\w -> w == wSpace ||
                                                                          w == wCR ||
                                                                          w == wLF))
  <?> "nicknamePrefix"

isWordAsciiUpper :: Word8 -> Bool
isWordAsciiUpper w = asciiToWord8 'A' <= w && w <= asciiToWord8 'Z'

digit :: Parser Word8
digit = satisfy (\w -> asciiToWord8 '0' <= w && w <= asciiToWord8 '9')

-- | Parse a command.  Either a string of capital letters, or 3 digits.
command :: Parser Command
command  = takeWhile1 isWordAsciiUpper
        <|> digitsToByteString <$>
                   digit
               <*> digit
               <*> digit
        <?> "command"
    where digitsToByteString x y z = pack [x,y,z]

-- | Parse a command parameter.
parameter :: Parser Parameter
parameter  =  (word8 wColon *> takeTill (\w -> w == wCR ||
                                               w == wLF))
          <|> takeTill (\w -> w == wSpace ||
                              w == wCR ||
                              w == wLF)
          <?> "parameter"

-- | Parse a cr lf
crlf :: Parser ()
crlf =  void (word8 wCR *> optional (word8 wLF))
    <|> void (word8 wLF)

-- | Parse a Message
message :: Parser Message
message  = Message <$>
      optionMaybe (tokenize prefix)
  <*> command
  <*> many (some spaces *> parameter)
  <*  optional crlf
  <*  endOfInput
  <?> "message"