lovecraftis sent (from the client to the server) like this:
"PRIVMSG lovecraft :hi there, man!\r\n"
Also, the message may include a sender field as its first field. This is indicated by the very first character of the message being a colon. When the above
PRIVMSGis sent by a user
cthulhuto the server, it redirects the message to the recipient, but now indicating who sent it; like this:
":cthulhu PRIVMSG lovecraft :hi there, man!\r\n"
So the task is to break an incoming IRC message into its constituent fields. There are two complications: 1) it's not possible to just split using spaces as separators, because of the last argument and 2) the first and last argument both may begin with a colon, and so it's not sufficient to just stop splitting at the first sight of a colon in the first character of a field.
I first defined a recursive function that did most of the work after a call to
String.split, but then thought about using higher-order functions. Two Haskell functions could be used, called
dropWhile. They are like
drop, but instead of using a numeric index to decide where to stop taking (or dropping) elements to (or from) the list, it uses a predicate testing over the elements. So,
takeWhile p lwill return a list taking elements
p xis true. Here is the code for these functions:
let rec takeWhile p l =
match l with
 -> 
| (x :: rl) when p x -> x :: (takeWhile p rl)
| _ -> 
let rec dropWhile p l =
match l with
 -> 
| (x :: rl) when p x -> dropWhile p rl
| _ -> l
Now it's easy to define the function
splitLinethat splits an incoming IRC message into fields, keeping the last field intact. It assumes that the trailing CR-LF characters where already stripped from the message.
let splitLine line =
let noColon s = s. <> ':' in
let concat l = match l with  ->  | _ -> [String.concat " " l] in
let words = line |> String.split [' '] in
match words with
 -> 
| (w :: wds) -> w :: ((takeWhile noColon wds) @
(concat (dropWhile noColon wds)))
So, to show an example:
":cthulhu PRIVMSG lovecraft :hi there, man!\r\n";;
val it : string list = [":cthulhu";
":hi there, man!"]
It's necessary, at this stage, to keep the colon in the sender field (to signal that the message has a sender field), but it could be removed from the last field. Anyway,
splitLinewill be used by another function that builds a
Messagerecord, removing all the colons that are added by the protocol.