Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
decoder.ml1 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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179type decoder = { buffer : Bytes.t ; mutable pos : int ; mutable max : int } let pp ppf { buffer; pos; max; } = Fmt.pf ppf "%S" (Bytes.sub_string buffer pos (max - pos)) let io_buffer_size = 65536 let decoder () = { buffer= Bytes.create io_buffer_size ; pos= 0 ; max= 0 } let decoder_from x = let max = String.length x in let buffer = Bytes.of_string x in { buffer; pos= 0; max; } type error = [ `End_of_input | `Expected_char of char | `Unexpected_char of char | `Expected_string of string (* | Invalid_command of string *) | `Expected_eol | `Expected_eol_or_space | `Not_enough_space | `Assert_predicate of (char -> bool) ] (* | Invalid_code of int *) let pp_error ppf = function | `End_of_input -> Fmt.string ppf "End of input" | `Expected_char chr -> Fmt.pf ppf "Expected char: %02x" (Char.code chr) | `Unexpected_char chr -> Fmt.pf ppf "Unexpected char: %02x" (Char.code chr) | `Expected_string s -> Fmt.pf ppf "Expected_string: %s" s | `Expected_eol -> Fmt.string ppf "Expected end-of-line" | `Expected_eol_or_space -> Fmt.string ppf "Expected end-of-line or space" | `Not_enough_space -> Fmt.string ppf "Not enough space" | `Assert_predicate _ -> Fmt.string ppf "Assert predicate" type 'err info = { error : 'err ; buffer : Bytes.t ; committed : int } type ('v, 'err) state = | Done of 'v | Read of { buffer : Bytes.t; off : int; len : int; continue : int -> ('v, 'err) state } | Error of 'err info exception Leave of error info let return (type v) (v : v) _ : (v, 'err) state = Done v let safe : (decoder -> ('v, [> error ] as 'err) state) -> decoder -> ('v, 'err) state = fun k decoder -> try k decoder with Leave { error= #error as error ; buffer ; committed } -> Error { error= (error :> 'err) ; buffer ; committed } let end_of_input decoder = decoder.max let peek_char decoder = if decoder.pos < end_of_input decoder then Some (Bytes.unsafe_get decoder.buffer decoder.pos) else None (* XXX(dinosaure): in [angstrom] world, [peek_char] should try to read input again. However, SMTP is a line-directed protocol where we can ensure to have the full line at the top (with a queue) instead to have a systematic check (which slow-down the process). *) let leave_with (decoder : decoder) error = raise (Leave { error; buffer= decoder.buffer; committed= decoder.pos; }) let fail (decoder : decoder) error = Error { error; buffer= decoder.buffer; committed= decoder.pos; } let string str decoder = let idx = ref 0 in let len = String.length str in while decoder.pos + !idx < end_of_input decoder && !idx < len && Char.equal (Bytes.unsafe_get decoder.buffer (decoder.pos + !idx)) (String.unsafe_get str !idx) do incr idx done ; if !idx = len then decoder.pos <- decoder.pos + len else leave_with decoder (`Expected_string str) let junk_char decoder = if decoder.pos < end_of_input decoder then decoder.pos <- decoder.pos + 1 else leave_with decoder `End_of_input let while1 predicate decoder = let idx = ref decoder.pos in while !idx < end_of_input decoder && predicate (Bytes.unsafe_get decoder.buffer !idx) do incr idx done ; if !idx - decoder.pos = 0 then leave_with decoder (`Assert_predicate predicate) ; let sub = decoder.buffer, decoder.pos, !idx - decoder.pos in (* XXX(dinosaure): avoid sub-string operation. *) decoder.pos <- !idx ; sub let at_least_one_line ?(relax= true) decoder = let pos = ref decoder.pos in let chr = ref '\000' in let has_cr = ref false in while !pos < decoder.max && ( chr := Bytes.unsafe_get decoder.buffer !pos ; not (!chr = '\n' && (!has_cr || relax)) ) do has_cr := !chr = '\r' ; incr pos done ; !pos < decoder.max && !chr = '\n' && (!has_cr || relax) let prompt : ?relax:bool -> (decoder -> ('v, [> error ] as 'err) state) -> decoder -> ('v, 'err) state = fun ?relax k decoder -> if decoder.pos > 0 then (* XXX(dinosaure): compress *) (let rest = decoder.max - decoder.pos in Bytes.unsafe_blit decoder.buffer decoder.pos decoder.buffer 0 rest ; decoder.max <- rest ; decoder.pos <- 0 ) ; let rec go off = if off = Bytes.length decoder.buffer then Error { error= `Not_enough_space ; buffer= decoder.buffer ; committed= decoder.pos } else if not (at_least_one_line ?relax { decoder with max= off }) (* XXX(dinosaure): we make a new decoder here and we did __not__ set [decoder.max] owned by end-user, and this is exactly what we want. *) then Read { buffer= decoder.buffer ; off ; len= Bytes.length decoder.buffer - off ; continue= (fun len -> go (off + len)) } else ( decoder.max <- off ; safe k decoder ) in go decoder.max let peek_while_eol ?(relax= false) decoder = let idx = ref decoder.pos in let chr = ref '\000' in let has_cr = ref false in while !idx < end_of_input decoder && ( chr := Bytes.unsafe_get decoder.buffer !idx ; not (!chr == '\n' && (!has_cr || relax)) ) do has_cr := !chr == '\r' ; incr idx done ; if !idx < end_of_input decoder && !chr == '\n' && (!has_cr || relax) then ( assert (!idx + 1 - decoder.pos > 0) ; decoder.buffer, decoder.pos, !idx + 1 - decoder.pos ) else leave_with decoder `Expected_eol let peek_while_eol_or_space ?(relax= false) decoder = let idx = ref decoder.pos in let chr = ref '\000' in let has_cr = ref false in while !idx < end_of_input decoder && ( chr := Bytes.unsafe_get decoder.buffer !idx ; not (!chr = '\n' && (!has_cr || relax)) && !chr <> ' ') do has_cr := !chr = '\r' ; incr idx done ; if !idx < end_of_input decoder && ((!chr = '\n' && (!has_cr || relax)) || (!chr = ' ')) then ( decoder.buffer, decoder.pos, !idx + 1 - decoder.pos ) else leave_with decoder `Expected_eol_or_space