text.bend source
text.bend on the hub · documented module
# Text: lines out of a byte stream, and header-safe strings. SMTP is a# line protocol: every command and reply line ends in CRLF, and a CR or# LF smuggled into a field would start a new command or header.import Base# A cut of a buffer: the first complete line and the rest, or the# whole buffer back when no line is complete yet.type Line is Data: Need{buf: String} Line{line: String, rest: String}# Lines# -----# A line read backwards, its CR dropped, put back in order.def Text.done(acc: String) -> String: match acc: case SNil{}: SNil{} case SCon{Chr{13}, t}: String.reverse(t) case SCon{c, t}: String.reverse(SCon{c, t})# The first LF-terminated line of s; Need{""} when there is none.def Text.line(s: String, acc: String) -> Line: match s: case SNil{}: Need{SNil{}} case SCon{Chr{10}, t}: Line{Text.done(acc), t} case SCon{c, t}: Text.line(t, SCon{c, acc})def Text.cut.fin(l: Line, buf: String) -> Line: match l: case Need{_}: Need{buf} case Line{a, b}: Line{a, b}# The first line of buf and the rest, or Need{buf}.def Text.cut(+buf: String) -> Line: Text.cut.fin(Text.line(buf, SNil{}), buf)# Safe fields# -----------def Text.is_nl(c: Char) -> Bool: match c: case Chr{+n}: U32.is_eq(n, 10) || U32.is_eq(n, 13)def Text.clean.put(nl: Bool, c: Char, rest: String) -> String: match nl: case True{}: rest case False{}: SCon{c, rest}# s without its CRs and LFs: safe inside one command or header line.def Text.clean(s: String) -> String: match s: case SNil{}: SNil{} case SCon{+c, t}: Text.clean.put(Text.is_nl(c), c, Text.clean(t))# Whether s holds a CR or an LF.def Text.has_nl(s: String) -> Bool: match s: case SNil{}: False{} case SCon{+c, t}: Text.is_nl(c) || Text.has_nl(t)# acc reversed in front of tail: the last step of a tail-recursive# builder (a non-tail recursion costs a frame per char in Bend).def Text.onto(acc: String, tail: String) -> String: match acc: case SNil{}: tail case SCon{c, t}: Text.onto(t, SCon{c, tail})