frame.bend

cswap-bend-cli@0.1.0.0 · 53 lines · 7 definitions

1import Base
2
3# Message framing for `cswap rpc` and `cswap serve`: a JSON message is one
4# line; a YAML or TOON message is a block of lines ended by a blank line.
5# `take` cuts the first complete message off a buffer. Pure.
6
7type Cut is Data:
8  Cut{msg: String, rest: String}
9
10def nl.pick(hit: Bool, n: Nat, rest: Maybe<&2, Nat>) -> Maybe<&2, Nat>:
11  match hit:
12    case True{}:
13      Some{n}
14    case False{}:
15      rest
16
17# Index of the first LF.
18def nl.find(s: String, +n: Nat) -> Maybe<&2, Nat>:
19  match s:
20    case SNil{}:
21      None{}
22    case SCon{Chr{+c}, t}:
23      nl.pick(U32.is_eq(c, 10), n, nl.find(t, Nat.add(n, 1n)))
24
25# Index of the LF that ends a blank line: an LF whose previous line break was
26# an LF too (CR before either is ignored). `prev`: the last significant
27# character was an LF (true at the start, so leading blank lines are cut).
28def blank.find(s: String, +n: Nat, +prev: Bool) -> Maybe<&2, Nat>:
29  match s:
30    case SNil{}:
31      None{}
32    case SCon{Chr{+c}, t}:
33      nl.pick(prev && U32.is_eq(c, 10), n, blank.find(t, Nat.add(n, 1n), Bool.pick(Bool, U32.is_eq(c, 13), prev, U32.is_eq(c, 10))))
34
35def cut.at(m: Maybe<&2, Nat>, +s: String) -> Maybe<&2, Cut>:
36  match m:
37    case None{}:
38      None{}
39    case Some{+n}:
40      Some{Cut{String.take(s, n), String.drop(s, Nat.add(n, 1n))}}
41
42# The first message of `buf` (json: a line; block: up to a blank line) and the
43# rest, none while the message is incomplete.
44def take(block: Bool, +buf: String) -> Maybe<&2, Cut>:
45  match block:
46    case True{}:
47      cut.at(blank.find(buf, 0n, True{}), buf)
48    case False{}:
49      cut.at(nl.find(buf, 0n), buf)
50
51# Is there anything but white space?
52def blank(+s: String) -> Bool:
53  String.is_empty(String.trim(s))