new improved message parsing
This commit is contained in:
parent
56dc7f2602
commit
4e43ecd20b
|
@ -1,4 +1,5 @@
|
||||||
(library
|
(library
|
||||||
(name ircd_msg)
|
(package talircd)
|
||||||
|
(name irc_msg)
|
||||||
(inline_tests)
|
(inline_tests)
|
||||||
(preprocess (pps ppx_expect ppx_deriving.show)))
|
(preprocess (pps ppx_expect ppx_deriving.show)))
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
type t = {
|
||||||
|
prefix : string option;
|
||||||
|
command : string;
|
||||||
|
params : string list;
|
||||||
|
trailing : bool;
|
||||||
|
} [@@deriving show { with_path = false }]
|
||||||
|
|
||||||
|
let rec is_params_trailing = function
|
||||||
|
| [] -> false
|
||||||
|
| [tr] -> String.contains tr ' '
|
||||||
|
| _ :: tl -> is_params_trailing tl
|
||||||
|
|
||||||
|
let make ?prefix ?(always_trailing = false) command params =
|
||||||
|
let trailing = always_trailing || is_params_trailing params in
|
||||||
|
{ prefix; command; params; trailing }
|
||||||
|
|
||||||
|
let write buf t =
|
||||||
|
Option.iter
|
||||||
|
(fun pre ->
|
||||||
|
Buffer.add_char buf ':';
|
||||||
|
Buffer.add_string buf pre;
|
||||||
|
Buffer.add_char buf ' ')
|
||||||
|
t.prefix;
|
||||||
|
Buffer.add_string buf t.command;
|
||||||
|
let rec add_params = function
|
||||||
|
| [] -> ()
|
||||||
|
| [tr] when t.trailing ->
|
||||||
|
Buffer.add_string buf " :";
|
||||||
|
Buffer.add_string buf tr
|
||||||
|
| hd::tl ->
|
||||||
|
Buffer.add_char buf ' ';
|
||||||
|
Buffer.add_string buf hd;
|
||||||
|
add_params tl
|
||||||
|
in
|
||||||
|
add_params t.params;
|
||||||
|
Buffer.add_string buf "\r\n"
|
||||||
|
|
||||||
|
exception Empty_message of int
|
||||||
|
exception Incomplete
|
||||||
|
|
||||||
|
let crlf = function '\r' | '\n' -> true | _ -> false
|
||||||
|
let cl = function ':' -> true | _ -> false
|
||||||
|
let sp = function ' ' -> true | _ -> false
|
||||||
|
let nocrlf = function '\r' | '\n' -> false | _ -> true
|
||||||
|
let nospcrlf = function ' ' | '\r' | '\n' -> false | _ -> true
|
||||||
|
|
||||||
|
let startswith str f i =
|
||||||
|
if i >= String.length str then
|
||||||
|
raise_notrace Incomplete;
|
||||||
|
f str.[i]
|
||||||
|
|
||||||
|
let rec skip str f i =
|
||||||
|
if i < String.length str && f str.[i] then
|
||||||
|
skip str f (i + 1)
|
||||||
|
else
|
||||||
|
i
|
||||||
|
|
||||||
|
let span str f i =
|
||||||
|
let e = skip str f i in
|
||||||
|
e, String.sub str i (e - i)
|
||||||
|
|
||||||
|
let parse str =
|
||||||
|
let parse_prefix i =
|
||||||
|
if startswith str cl i then
|
||||||
|
let i, pfx = span str nospcrlf (i + 1) in
|
||||||
|
i, Some pfx
|
||||||
|
else
|
||||||
|
i, None
|
||||||
|
in
|
||||||
|
|
||||||
|
let parse_command i =
|
||||||
|
let i = skip str sp i in
|
||||||
|
if startswith str crlf i then
|
||||||
|
raise_notrace (Empty_message (skip str crlf i));
|
||||||
|
let i, cmd = span str nospcrlf i in
|
||||||
|
i, String.uppercase_ascii cmd
|
||||||
|
in
|
||||||
|
|
||||||
|
let rec parse_params acc i =
|
||||||
|
let i = skip str sp i in
|
||||||
|
if startswith str crlf i then
|
||||||
|
let i = skip str crlf i in
|
||||||
|
i, List.rev acc, false
|
||||||
|
else if startswith str cl i then
|
||||||
|
let i, par = span str nocrlf (i + 1) in
|
||||||
|
i, List.rev (par :: acc), true
|
||||||
|
else
|
||||||
|
let i, par = span str nospcrlf i in
|
||||||
|
parse_params (par :: acc) i
|
||||||
|
in
|
||||||
|
|
||||||
|
let parse_msg i =
|
||||||
|
let i, prefix = parse_prefix i in
|
||||||
|
let i, command = parse_command i in
|
||||||
|
let i, params, always_trailing = parse_params [] i in
|
||||||
|
let msg = make command params ?prefix ~always_trailing in
|
||||||
|
i, msg
|
||||||
|
in
|
||||||
|
|
||||||
|
let rec parse_all acc i =
|
||||||
|
match parse_msg i with
|
||||||
|
| i, msg -> parse_all (msg :: acc) i
|
||||||
|
| exception Empty_message i -> parse_all acc i
|
||||||
|
| exception Incomplete -> i, List.rev acc
|
||||||
|
in
|
||||||
|
|
||||||
|
let i, msgs = parse_all [] 0 in
|
||||||
|
let rest = if i = 0 then str else String.sub str i (String.length str - i) in
|
||||||
|
msgs, rest
|
||||||
|
|
||||||
|
;;
|
||||||
|
|
||||||
|
let%expect_test _ =
|
||||||
|
let print_msg_nl m =
|
||||||
|
let buf = Buffer.create 64 in
|
||||||
|
write buf m;
|
||||||
|
Printf.printf "%S\n" (Buffer.contents buf)
|
||||||
|
in
|
||||||
|
let print_parsed_msgs (msgs, rest) =
|
||||||
|
List.iter
|
||||||
|
(fun m -> Format.kasprintf print_string "%a\n" pp m)
|
||||||
|
msgs;
|
||||||
|
Format.printf "%S\n" rest
|
||||||
|
in
|
||||||
|
|
||||||
|
make "NICK" ["tali"] |> print_msg_nl;
|
||||||
|
[%expect {| "NICK tali\r\n" |}];
|
||||||
|
|
||||||
|
make "USER" ["milo"; "0"; "*"; "milo"] ~always_trailing:true |> print_msg_nl;
|
||||||
|
[%expect {| "USER milo 0 * :milo\r\n" |}];
|
||||||
|
|
||||||
|
make "001" ["tali"; "Welcome to the IRC Network"] ~prefix:"localhost" |> print_msg_nl;
|
||||||
|
[%expect {| ":localhost 001 tali :Welcome to the IRC Network\r\n" |}];
|
||||||
|
|
||||||
|
parse ":source usEr tali 0 * iitalics\r\n" |> print_parsed_msgs;
|
||||||
|
[%expect {|
|
||||||
|
{ prefix = (Some "source"); command = "USER";
|
||||||
|
params = ["tali"; "0"; "*"; "iitalics"]; trailing = false }
|
||||||
|
""
|
||||||
|
|}];
|
||||||
|
|
||||||
|
parse "PRIVMSG #lol :Hello world\r\nPRIVMSG #lol :meow\r\nPRIVM" |> print_parsed_msgs;
|
||||||
|
[%expect {|
|
||||||
|
{ prefix = None; command = "PRIVMSG"; params = ["#lol"; "Hello world"];
|
||||||
|
trailing = true }
|
||||||
|
{ prefix = None; command = "PRIVMSG"; params = ["#lol"; "meow"];
|
||||||
|
trailing = true }
|
||||||
|
"PRIVM"
|
||||||
|
|}];
|
||||||
|
|
||||||
|
parse " \r\n" |> print_parsed_msgs;
|
||||||
|
[%expect {| "" |}];
|
||||||
|
|
||||||
|
parse ":ignore-me \r\nFOO\r\n" |> print_parsed_msgs;
|
||||||
|
[%expect {|
|
||||||
|
{ prefix = None; command = "FOO"; params = []; trailing = false }
|
||||||
|
""
|
||||||
|
|}];
|
||||||
|
|
|
@ -11,4 +11,4 @@ val make : ?prefix:string -> ?always_trailing:bool ->
|
||||||
string -> string list -> t
|
string -> string list -> t
|
||||||
|
|
||||||
val write : Buffer.t -> t -> unit
|
val write : Buffer.t -> t -> unit
|
||||||
val parse : ?ofs:int -> ?len:int -> string -> t option
|
val parse : string -> t list * string
|
|
@ -1,127 +0,0 @@
|
||||||
type t = {
|
|
||||||
prefix : string option;
|
|
||||||
command : string;
|
|
||||||
params : string list;
|
|
||||||
trailing : bool;
|
|
||||||
} [@@deriving show { with_path = false }]
|
|
||||||
|
|
||||||
let rec is_params_trailing = function
|
|
||||||
| [] -> false
|
|
||||||
| [tr] -> String.contains tr ' '
|
|
||||||
| _ :: tl -> is_params_trailing tl
|
|
||||||
|
|
||||||
let make ?prefix ?(always_trailing = false) command params =
|
|
||||||
let trailing = always_trailing || is_params_trailing params in
|
|
||||||
{ prefix; command; params; trailing }
|
|
||||||
|
|
||||||
let write buf t =
|
|
||||||
Option.iter
|
|
||||||
(fun pre ->
|
|
||||||
Buffer.add_char buf ':';
|
|
||||||
Buffer.add_string buf pre;
|
|
||||||
Buffer.add_char buf ' ')
|
|
||||||
t.prefix;
|
|
||||||
Buffer.add_string buf t.command;
|
|
||||||
let rec add_params = function
|
|
||||||
| [] -> ()
|
|
||||||
| [tr] when t.trailing ->
|
|
||||||
Buffer.add_string buf " :";
|
|
||||||
Buffer.add_string buf tr
|
|
||||||
| hd::tl ->
|
|
||||||
Buffer.add_char buf ' ';
|
|
||||||
Buffer.add_string buf hd;
|
|
||||||
add_params tl
|
|
||||||
in
|
|
||||||
add_params t.params;
|
|
||||||
Buffer.add_string buf "\r\n"
|
|
||||||
|
|
||||||
exception Empty_message
|
|
||||||
|
|
||||||
let parse str i endp =
|
|
||||||
let startswith f i = if i >= endp then false else f str.[i] in
|
|
||||||
let rec span f i = if startswith f i then span f (i + 1) else i in
|
|
||||||
let cl = function ':' -> true | _ -> false in
|
|
||||||
let sp = function ' ' -> true | _ -> false in
|
|
||||||
let nosp = function ' ' -> false | _ -> true in
|
|
||||||
|
|
||||||
let i, prefix =
|
|
||||||
if startswith cl i then
|
|
||||||
let i_s = i + 1 in
|
|
||||||
let i_e = span nosp i_s in
|
|
||||||
let prefix = String.sub str i_s (i_e - i_s) in
|
|
||||||
i_e, Some prefix
|
|
||||||
else
|
|
||||||
i, None
|
|
||||||
in
|
|
||||||
|
|
||||||
let i, command =
|
|
||||||
let i_s = span sp i in
|
|
||||||
if i_s >= endp then raise_notrace Empty_message;
|
|
||||||
let i_e = span nosp i_s in
|
|
||||||
let command = String.sub str i_s (i_e - i_s) |> String.uppercase_ascii in
|
|
||||||
i_e, command
|
|
||||||
in
|
|
||||||
|
|
||||||
let rec params acc i =
|
|
||||||
let i_s = span sp i in
|
|
||||||
if i_s >= endp then
|
|
||||||
List.rev acc, false
|
|
||||||
else if startswith cl i_s then
|
|
||||||
let i_s = i_s + 1 in
|
|
||||||
let i_e = endp in
|
|
||||||
let trailing = String.sub str i_s (i_e - i_s) in
|
|
||||||
List.rev (trailing :: acc), true
|
|
||||||
else
|
|
||||||
let i_e = span nosp i_s in
|
|
||||||
let param = String.sub str i_s (i_e - i_s) in
|
|
||||||
params (param :: acc) i_e
|
|
||||||
in
|
|
||||||
|
|
||||||
let params, always_trailing = params [] i in
|
|
||||||
make command params ?prefix ~always_trailing
|
|
||||||
|
|
||||||
let parse ?(ofs = 0) ?(len = max_int) str =
|
|
||||||
let endp = ofs + min len (String.length str) in
|
|
||||||
try Some (parse str ofs endp)
|
|
||||||
with Empty_message -> None
|
|
||||||
|
|
||||||
;;
|
|
||||||
|
|
||||||
let%expect_test _ =
|
|
||||||
let print_msg_nl m =
|
|
||||||
let buf = Buffer.create 64 in
|
|
||||||
write buf m;
|
|
||||||
Printf.printf "%S\n" (Buffer.contents buf)
|
|
||||||
in
|
|
||||||
let print_parsed_msg_nl = function
|
|
||||||
| Some m -> Format.kasprintf print_string "%a\n" pp m
|
|
||||||
| None -> print_endline "empty"
|
|
||||||
in
|
|
||||||
|
|
||||||
make "NICK" ["tali"] |> print_msg_nl;
|
|
||||||
[%expect {| "NICK tali\r\n" |}];
|
|
||||||
|
|
||||||
make "USER" ["milo"; "0"; "*"; "milo"] ~always_trailing:true |> print_msg_nl;
|
|
||||||
[%expect {| "USER milo 0 * :milo\r\n" |}];
|
|
||||||
|
|
||||||
make "001" ["tali"; "Welcome to the IRC Network"] ~prefix:"localhost" |> print_msg_nl;
|
|
||||||
[%expect {| ":localhost 001 tali :Welcome to the IRC Network\r\n" |}];
|
|
||||||
|
|
||||||
parse ":source usEr tali 0 * iitalics" |> print_parsed_msg_nl;
|
|
||||||
[%expect {|
|
|
||||||
{ prefix = (Some "source"); command = "USER";
|
|
||||||
params = ["tali"; "0"; "*"; "iitalics"]; trailing = false }
|
|
||||||
|}];
|
|
||||||
|
|
||||||
parse "PRIVMSG #lol :Hello world" |> print_parsed_msg_nl;
|
|
||||||
[%expect {|
|
|
||||||
{ prefix = None; command = "PRIVMSG"; params = ["#lol"; "Hello world"];
|
|
||||||
trailing = true }
|
|
||||||
|}];
|
|
||||||
|
|
||||||
parse " " |> print_parsed_msg_nl;
|
|
||||||
[%expect {| empty |}];
|
|
||||||
|
|
||||||
parse ":source " |> print_parsed_msg_nl;
|
|
||||||
[%expect {| empty |}];
|
|
||||||
|
|
Loading…
Reference in New Issue