Created
December 14, 2014 20:11
-
-
Save rgrinberg/d82e7c66b71c76ad7ce3 to your computer and use it in GitHub Desktop.
Revisions
-
rgrinberg created this gist
Dec 14, 2014 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,133 @@ open Core.Std module Substring = struct include Substring let get t i = (Substring.base t).[(Substring.pos t) + i] let drop_prefix_str t ~prefix = let len = String.length prefix in if len > Substring.length t then None else try for i = 0 to len - 1 do if get t i <> prefix.[i] then raise_notrace Exit done; Some (Substring.drop_prefix t len) with Exit -> None let take_while_i t ~f = let len = Substring.length t in let rec loop i = if i = len then i else if f (get t i) then loop (i + 1) else i in loop 0 let take_while t ~f = let i = take_while_i t ~f in (i |> Substring.prefix t, Substring.drop_prefix t i) let drop_while t ~f = let drop_count = take_while_i t ~f:(Fn.compose not f) in Substring.drop_prefix t drop_count end module type Parser_intf = sig type 'a t (* parser that produces a value of type 'a *) (* monadic operations *) val return : 'a -> 'a t val (>>=) : 'a t -> ('a -> 'b t) -> 'b t (* validate/map input with f. If f returns none then the parser fails. If f return Some x then x is the result returned *) val filter_map : 'a t -> f:('a -> 'b option) -> 'b t (* run the parser against the string and return None if parsing fails. On success Some (x, rest) where x is the result and rest is the remaining string that needs to be parsed *) val run : 'a t -> Substring.t -> ('a * Substring.t) option (* little helpers *) val drop_prefix : string -> unit t val drop_while : (char -> bool) -> unit t val take_while : (char -> bool) -> string t end module Parser = struct type 'a t = Substring.t -> ('a * Substring.t) option let drop_while f t = Some ((), Substring.drop_while t ~f) let drop_prefix prefix t = Substring.drop_prefix_str t ~prefix |> Option.map ~f:(fun s -> ((), s)) let take_while f t = t |> Substring.take_while ~f |> Tuple2.map1 ~f:Substring.to_string |> Option.some let filter_map t ~f x = let open Option.Monad_infix in t x >>= fun (s, rest) -> f s >>| (fun x -> (x, rest)) let run t s = t s type 'a tt = 'a t include Monad.Make(struct type 'a t = 'a tt let return x s = Some (x, s) let bind t f s = match t s with | None -> None | Some (x, s') -> let t' = f x in t' s' let map = `Define_using_bind end) end let () = let module M = (Parser : Parser_intf) in () type (_, _) t = | Try_parse : unit Parser.t -> ('a, 'a) t | Parse : 'b Parser.t -> ('a, 'b -> 'a) t | Concat : ('b, 'c) t * ('a, 'b) t -> ('a, 'c) t let rec ints : type a b . (a, b) t -> b -> a Parser.t = let open Option.Monad_infix in fun t f inp -> match t with | Try_parse p -> Parser.run p inp >>| fun ((), inp') -> (f, inp') | Parse p -> Parser.run p inp >>| fun (v, s) -> (f v, s) | Concat (a, b) -> ints a f inp >>= fun (vb, inp') -> ints b vb inp' let match_url t s cb = let s = Substring.of_string s in match ints t cb s with | None -> None | Some (x, subs) -> if subs |> Substring.to_string |> String.is_empty then Some x else (* we did not consume the whole string so no match *) None (* little combinator that guarantees that the parsed string isn't empty *) let non_empty = Parser.filter_map ~f:(fun x -> if String.is_empty x then None else Some x) let int = Parse (fun x -> (Char.is_digit |> Parser.take_while |> non_empty |> Parser.map ~f:Int.of_string) x) let s x = Try_parse (Parser.drop_prefix x) let (</>) x1 x2 = let lead_slash x = Concat (s "/", x) in Concat (x1, lead_slash x2) let str = Parse (fun x -> (Parser.take_while ((<>) '/') |> non_empty) x)