~/bend-docscommunity

reply.bend source

reply.bend on the hub · documented module

# Replies: a server answers each command with one or more lines, each a# 3-digit code and a separator: "250-" means more lines follow, "250 "# (or the bare code) ends the reply. RFC 5321, section 4.2.import Baseimport ./text.bend as T# A whole reply: its code and its lines' text, joined by LF.type Reply is Data:  Reply{code: U32, text: String}# A buffer cut at the end of its first reply, or kept whole until one# arrives in full. A malformed line ends the reply with code 0.type Split is Data:  More{buf: String}  Got{reply: Reply, rest: String}# One reply line, read.type Rl is Data:  Bad{}  Rl{code: U32, last: Bool, text: String}# Lines# -----def Reply.digit(+c: U32) -> Bool:  ((c >= 48) && (c <= 57) : U32)def Reply.sep(code: U32, rest: String) -> Rl:  match rest:    case SNil{}:      Rl{code, True{}, SNil{}}    case SCon{Chr{45}, t}:      Rl{code, False{}, t}    case SCon{_, t}:      Rl{code, True{}, t}def Reply.ok(ok: Bool, code: U32, rest: String) -> Rl:  match ok:    case True{}:      Reply.sep(code, rest)    case False{}:      Bad{}def Reply.line(l: String) -> Rl:  match l:    case SCon{Chr{+a}, SCon{Chr{+b}, SCon{Chr{+c}, rest}}}:      Reply.ok(Reply.digit(a) && Reply.digit(b) && Reply.digit(c),        (a * 100 + b * 10 + c - 5328 : U32), rest)    case _:      Bad{}# Scan# ----def Reply.end(last: Bool, code: U32, rest: String, text: String,  more: String -> String -> Split) -> Split:  match last:    case True{}:      Got{Reply{code, text}, rest}    case False{}:      more(rest, text ++ "\n")def Reply.rl(rl: Rl, rest: String, text: String,  more: String -> String -> Split) -> Split:  match rl:    case Bad{}:      Got{Reply{0, text}, rest}    case Rl{code, last, t}:      Reply.end(last, code, rest, text ++ t, more)def Reply.step(l: T.Line, text: String, more: String -> String -> Split) ->  Split:  match l:    case T.Need{_}:      More{SNil{}}    case T.Line{line, rest}:      Reply.rl(Reply.line(line), rest, text, more)# Each line eats at least one byte, so fuel = length + 1 always lasts.def Reply.scan(fuel: Nat, s: String, text: String) -> Split:  match fuel:    case 0n:      More{SNil{}}    case 1n+p:      Reply.step(T.Text.line(s, SNil{}), text, r => t => Reply.scan(p, r, t))def Reply.fin(sp: Split, buf: String) -> Split:  match sp:    case More{_}:      More{buf}    case Got{r, rest}:      Got{r, rest}# The first complete reply in buf, or More{buf}.def Reply.split(+buf: String) -> Split:  Reply.fin(Reply.scan(1n+String.length(buf), buf, SNil{}), buf)# The reply's class: 2 done, 3 go on, 4 try later, 5 refused.def Reply.class(r: Reply) -> U32:  Reply{code, _} = r  (code / 100 : U32)def Reply.code(r: Reply) -> U32:  Reply{code, _} = r  codedef Reply.text(r: Reply) -> String:  Reply{_, text} = r  text