1 import Base 2 import ../StorageBytes.bend as StorageBytes 3 import ./Protocol.bend as Protocol 4 import bend-kit-bytes@0.3.2.0/bytes.bend as Bytes 5 6 # Carries visible data and live owner-retention counters. 7 type Stats is Data: 8 ByteStats{entries: Nat, payload_bytes: Nat, snapshots: Nat, retained_bytes: Nat, cursors: Nat} 9 10 # Distinguishes a decoded statistics payload from malformed bytes. 11 type Decision is Data: 12 Parsed{stats: Stats} 13 Malformed{} 14 15 # Tracks decimal fields while parsing the bounded owner counter payload. 16 type State is Data: 17 EntryDigits{entries: Nat, seen: Bool} 18 PayloadDigits{entries: Nat, payload_bytes: Nat, seen: Bool} 19 SnapshotDigits{entries: Nat, payload_bytes: Nat, snapshots: Nat, seen: Bool} 20 RetainedDigits{entries: Nat, payload_bytes: Nat, snapshots: Nat, 21 retained_bytes: Nat, seen: Bool} 22 CursorDigits{entries: Nat, payload_bytes: Nat, snapshots: Nat, 23 retained_bytes: Nat, cursors: Nat, seen: Bool} 24 Invalid{} 25 26 def decimal.length(value: Nat) -> Nat: 27 String.length(Nat.show(value)) 28 29 def payload.length( 30 entries: Nat, 31 payload_bytes: Nat, 32 snapshots: Nat, 33 retained_bytes: Nat, 34 cursors: Nat 35 ) -> Nat: 36 Nat.add(4n, Nat.add(decimal.length(entries), 37 Nat.add(decimal.length(payload_bytes), 38 Nat.add(decimal.length(snapshots), 39 Nat.add(decimal.length(retained_bytes), decimal.length(cursors)))))) 40 41 def digit.value(scalar: U32) -> Nat: 42 Nat.sub(U32.to_nat(scalar), 48n) 43 44 def digit.valid.checked(copies: U32 & U32) -> Bool: 45 match copies: 46 case (lower, upper): U32.is_le(48, lower) && U32.is_le(upper, 57) 47 48 def digit.valid.copies(copies: U32 & U32 & U32) -> Bool: 49 match copies: 50 case (_, (lower, upper)): digit.valid.checked((lower, upper)) 51 52 def digit.valid(scalar: U32) -> Bool: 53 digit.valid.copies(Protocol.u32.duplicate.triple(scalar)) 54 55 def separator.character(parser: State, scalar: U32) -> State: 56 match parser: 57 case EntryDigits{entries, _}: 58 match scalar: 59 case 58: PayloadDigits{entries, 0n, False{}} 60 case _: Invalid{} 61 case PayloadDigits{entries, payload_bytes, _}: 62 match scalar: 63 case 58: SnapshotDigits{entries, payload_bytes, 0n, False{}} 64 case _: Invalid{} 65 case SnapshotDigits{entries, payload_bytes, snapshots, _}: 66 match scalar: 67 case 58: RetainedDigits{entries, payload_bytes, snapshots, 0n, False{}} 68 case _: Invalid{} 69 case RetainedDigits{entries, payload_bytes, snapshots, retained_bytes, _}: 70 match scalar: 71 case 58: 72 CursorDigits{entries, payload_bytes, snapshots, retained_bytes, 0n, 73 False{}} 74 case _: Invalid{} 75 case _: Invalid{} 76 77 # Advances to a new stats field only after the current field has digits. 78 def separator(parser: State, seen: Bool, scalar: U32) -> State: 79 match seen: 80 case False{}: Invalid{} 81 case True{}: separator.character(parser, scalar) 82 83 def entry.step( 84 entries: Nat, 85 scalar: U32, 86 is_digit: Bool, 87 seen: Bool 88 ) -> State: 89 match is_digit: 90 case True{}: 91 EntryDigits{Nat.add(Nat.mul(entries, 10n), digit.value(scalar)), True{}} 92 case False{}: 93 match seen: 94 case False{}: Invalid{} 95 case True{}: separator(EntryDigits{entries, True{}}, True{}, scalar) 96 97 def payload.step( 98 entries: Nat, 99 payload_bytes: Nat, 100 scalar: U32, 101 is_digit: Bool, 102 seen: Bool 103 ) -> State: 104 match is_digit: 105 case True{}: 106 PayloadDigits{entries, 107 Nat.add(Nat.mul(payload_bytes, 10n), digit.value(scalar)), True{}} 108 case False{}: 109 match seen: 110 case False{}: Invalid{} 111 case True{}: 112 separator(PayloadDigits{entries, payload_bytes, True{}}, True{}, 113 scalar) 114 115 def snapshot.step( 116 entries: Nat, 117 payload_bytes: Nat, 118 snapshots: Nat, 119 scalar: U32, 120 is_digit: Bool, 121 seen: Bool 122 ) -> State: 123 match is_digit: 124 case True{}: 125 SnapshotDigits{entries, payload_bytes, 126 Nat.add(Nat.mul(snapshots, 10n), digit.value(scalar)), True{}} 127 case False{}: 128 match seen: 129 case False{}: Invalid{} 130 case True{}: 131 separator(SnapshotDigits{entries, payload_bytes, snapshots, True{}}, 132 True{}, scalar) 133 134 def retained.step( 135 entries: Nat, 136 payload_bytes: Nat, 137 snapshots: Nat, 138 retained_bytes: Nat, 139 scalar: U32, 140 is_digit: Bool, 141 seen: Bool 142 ) -> State: 143 match is_digit: 144 case True{}: 145 RetainedDigits{entries, payload_bytes, snapshots, 146 Nat.add(Nat.mul(retained_bytes, 10n), digit.value(scalar)), True{}} 147 case False{}: 148 match seen: 149 case False{}: Invalid{} 150 case True{}: 151 separator(RetainedDigits{entries, payload_bytes, snapshots, 152 retained_bytes, True{}}, True{}, scalar) 153 154 def cursor.step( 155 entries: Nat, 156 payload_bytes: Nat, 157 snapshots: Nat, 158 retained_bytes: Nat, 159 cursors: Nat, 160 scalar: U32, 161 is_digit: Bool 162 ) -> State: 163 match is_digit: 164 case True{}: 165 CursorDigits{entries, payload_bytes, snapshots, retained_bytes, 166 Nat.add(Nat.mul(cursors, 10n), digit.value(scalar)), True{}} 167 case False{}: Invalid{} 168 169 def parse.step(state: State, scalar: U32, is_digit: Bool) -> State: 170 match state: 171 case EntryDigits{entries, seen}: 172 entry.step(entries, scalar, is_digit, seen) 173 case PayloadDigits{entries, payload_bytes, seen}: 174 payload.step(entries, payload_bytes, scalar, is_digit, seen) 175 case SnapshotDigits{entries, payload_bytes, snapshots, seen}: 176 snapshot.step(entries, payload_bytes, snapshots, scalar, is_digit, seen) 177 case RetainedDigits{entries, payload_bytes, snapshots, retained_bytes, seen}: 178 retained.step(entries, payload_bytes, snapshots, retained_bytes, 179 scalar, is_digit, seen) 180 case CursorDigits{entries, payload_bytes, snapshots, retained_bytes, 181 cursors, seen}: 182 cursor.step(entries, payload_bytes, snapshots, retained_bytes, cursors, 183 scalar, is_digit) 184 case Invalid{}: Invalid{} 185 186 def parse.step.copies( 187 state: State, 188 copies: U32 & U32 & U32 189 ) -> State: 190 match copies: 191 case (scalar, (checked_scalar, _)): 192 parse.step(state, scalar, digit.valid(checked_scalar)) 193 194 def parse.go(text: String, state: State) -> State: 195 match text: 196 case SNil{}: state 197 case SCon{Chr{scalar}, rest}: 198 parse.go(rest, parse.step.copies(state, 199 Protocol.u32.duplicate.triple(scalar))) 200 201 # Completes parsing only when the payload byte field contains digits. 202 def finish(state: State) -> Decision: 203 match state: 204 case CursorDigits{entries, payload_bytes, snapshots, retained_bytes, 205 cursors, True{}}: 206 Parsed{ByteStats{entries, payload_bytes, snapshots, retained_bytes, 207 cursors}} 208 case _: Malformed{} 209 210 def parse.text(text: String) -> Decision: 211 finish(parse.go(text, EntryDigits{0n, False{}})) 212 213 def parse.result( 214 result: Result<&1, &1, U32 & String, String> 215 ) -> Decision: 216 match result: 217 case Done{text}: parse.text(text) 218 case Fail{_}: Malformed{} 219 220 # Parses a strict UTF-8 decimal pair from the owner response bytes. 221 def parse(input: Bytes.Bytes) -> Decision: 222 parse.result(StorageBytes.to_string_strict(input))