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