src/pull.bend source
src/pull.bend on the hub · documented module
# src/pull: one JSON text, one event at a time.# `cursor` holds the unread suffix of the source. `next` returns one event and# the cursor after it. `skip` drops one value. A span event keeps the suffix# it points at, so it stays readable while that event is held and is gone# when the event is dropped. Commas and colons are not events.import Baseimport ./value.bend as Vimport ./lex.bend as Lex# where the next token of an open array or object has to betype Slot is Data: SVal{} SCom{} SKey{} SCol{}# an open array or object. `fresh` is the empty container, before any valuetype Frame is Data: FArr{slot: Slot, fresh: Bool} FObj{slot: Slot, fresh: Bool}# one event. A span is the first `nn` chars of a source suffix, not a copytype Ev is Data: ENull{} EBool{b: Bool} ENum{src: String, nn: U32} EStr{body: String} EStrS{src: String, nn: U32} EKey{body: String} EKeyS{src: String, nn: U32} EBeginArr{} EEndArr{} EBeginObj{} EEndObj{} EEnd{} EErr{}# the cursor. `rest` is the unread suffix. `done` is set once the root value# has ended. `bad` sticks after the first errortype Cur is Data: Cur{rest: String, stack: List<&2, Frame>, done: Bool, bad: Bool}# read the next event, or drop one whole valuetype Goal is Data: GNext{} GSkip{depth: Nat}# what a step hands back: an event, or a finished skiptype Stop is Data: SEv{ev: Ev} SSkip{}# a bare word, once it has been classifiedtype Atom is Data: ANull{} ATrue{} AFalse{} ANum{} ABad{}# a string's text: a span of the source, or chars decoded from escapestype Piece is Data: PSpan{cut: String, nn: U32} POwn{body: String}# what one character of a string does. The usual one is KGo: stay in the# string. The others leave ittype Skind is Data: KGo{} KEnd{} KEsc{} KBad{}# where a finished value lands, or that the slot cannot take onetype Spot is Data: Spot{stack: List<&2, Frame>, root: Bool} SpotBad{}# what the scanner is inside. Skip-modes (`MSk*`) walk a string they will# not keep, so a dropped value does not build its texttype Mode is Data: MIdle{} # a bare word's characters, reversed, so the unread array is not kept MWord{buf: List<&2, Char>, nn: U32, num: V.Num} MSpan{cut: String, nn: U32} MCopy{buf: List<&2, Char>} MEsc{buf: List<&2, Char>} MUni{left: Nat, acc: U32, buf: List<&2, Char>} MHi{hi: U32, buf: List<&2, Char>} MHiEsc{hi: U32, buf: List<&2, Char>} MLo{left: Nat, acc: U32, hi: U32, buf: List<&2, Char>} MSkStr{} MSkEsc{} MSkUni{left: Nat, acc: U32} MSkHi{hi: U32} MSkHiEsc{hi: U32} MSkLo{left: Nat, acc: U32, hi: U32} MDone{stop: Stop, saved: String} # the cursor rest is the suffix the stepper is already on, so closing a # long string does not keep that suffix a second time MRest{stop: Stop} # the quote is consumed; the next character is the first of the string MQuote{} # resume `mode` on `rest` after a plain word, which the stepper did not # walk byte by byte MJump{rest: String, mode: Mode}# the scanner's state, apart from the unread suffix it is walkingtype St is Data: S{mode: Mode, stack: List<&2, Frame>, goal: Goal, done: Bool, bad: Bool}# what a walk hands back: a finished step, or a budget used up, to resume on# `txt` in state `st`type Out is Type: Fin{got: (Stop & Cur)} More{txt: String, st: St}# a failed step. The unread suffix is droppeddef err.st(goal: Goal) -> St: S{MDone{SEv{EErr{}}, ""}, Nil{}, goal, False{}, True{}}# a failed cursordef bad.cur() -> (Stop & Cur): (SEv{EErr{}}, Cur{"", Nil{}, False{}, True{}})# the first `nn` chars of `src`, reversed, for the escape copierdef copy.rev(src: String, +nn: U32, zero: Bool, acc: List<&2, Char>) -> List<&2, Char>: match src zero: case s True{}: acc case SNil{} False{}: acc case SCon{h, t} False{}: copy.rev(t, U32.sub(nn, 1), U32.is_zero(U32.sub(nn, 1)), h <> acc)# a value can start heredef expect.val.go(stack: List<&2, Frame>) -> Bool: match stack: case Nil{}: True{} case Con{FArr{SVal{}, fresh}, rest}: True{} case Con{FObj{SVal{}, fresh}, rest}: True{} case other: False{}# a value can start here, and the root is not already finisheddef expect.val(stack: List<&2, Frame>, done: Bool) -> Bool: match done: case True{}: False{} case False{}: expect.val.go(stack)# a string can start here: a value, or an object keydef expect.str.go(stack: List<&2, Frame>) -> Bool: match stack: case Nil{}: True{} case Con{FArr{SVal{}, fresh}, rest}: True{} case Con{FObj{SVal{}, fresh}, rest}: True{} case Con{FObj{SKey{}, fresh}, rest}: True{} case other: False{}# a string can start here, and the root is not already finisheddef expect.str(stack: List<&2, Frame>, done: Bool) -> Bool: match done: case True{}: False{} case False{}: expect.str.go(stack)# a string can start here for this goal. A skip of one whole value must# start a value, so a key there fails at once, as it would once readdef expect.str.for(stack: List<&2, Frame>, goal: Goal, done: Bool) -> Bool: match goal: case GSkip{0n}: expect.val(stack, done) case _: expect.str(stack, done)# the stack after a value, and whether that value was the rootdef place.spot(stack: List<&2, Frame>) -> Spot: match stack: case Nil{}: Spot{Nil{}, True{}} case Con{FArr{SVal{}, fresh}, frames}: Spot{FArr{SCom{}, False{}} <> frames, False{}} case Con{FObj{SVal{}, fresh}, frames}: Spot{FObj{SCom{}, False{}} <> frames, False{}} case other: SpotBad{}# a value has landed in a slot that can take itdef place.val.ok(ev: Ev, rest: String, goal: Goal, stack: List<&2, Frame>, root: Bool) -> St: match goal: case GNext{}: S{MDone{SEv{ev}, rest}, stack, GNext{}, root, False{}} case GSkip{0n}: S{MDone{SSkip{}, rest}, stack, GSkip{0n}, root, False{}} case GSkip{1n+left}: S{MIdle{}, stack, GSkip{1n+left}, False{}, False{}}# a value has landed. `rest` is the suffix the cursor keepsdef place.val.on(ev: Ev, rest: String, goal: Goal, spot: Spot) -> St: match spot: case SpotBad{}: err.st(goal) case Spot{stack, root}: place.val.ok(ev, rest, goal, stack, root)# a value has landeddef place.val(ev: Ev, rest: String, stack: List<&2, Frame>, goal: Goal) -> St: place.val.on(ev, rest, goal, place.spot(stack))# an object key has landed. The value comes after the colondef place.key(ev: Ev, rest: String, frames: List<&2, Frame>, goal: Goal) -> St: match goal: case GNext{}: S{MDone{SEv{ev}, rest}, FObj{SCol{}, False{}} <> frames, goal, False{}, False{}} case GSkip{0n}: err.st(goal) case GSkip{1n+left}: S{MIdle{}, FObj{SCol{}, False{}} <> frames, goal, False{}, False{}}# a string event, span or owned, as a key or a valuedef str.ev(piece: Piece, key: Bool) -> Ev: match piece key: case PSpan{cut, nn} True{}: EKeyS{cut, nn} case PSpan{cut, nn} False{}: EStrS{cut, nn} case POwn{body} True{}: EKey{body} case POwn{body} False{}: EStr{body}# a string value inside a skip, once the slot is knowndef str.cont.on(goal: Goal, spot: Spot) -> St: match spot: case SpotBad{}: err.st(goal) case Spot{stack2, root}: S{MIdle{}, stack2, goal, False{}, False{}}# a string value inside a skip: keep the slot, not the textdef str.cont(stack: List<&2, Frame>, goal: Goal) -> St: str.cont.on(goal, place.spot(stack))# a string value. A skip that is still inside a container keeps scanningdef str.val(ev: Ev, rest: String, stack: List<&2, Frame>, goal: Goal) -> St: match goal: case GSkip{1n+left}: str.cont(stack, goal) case GNext{}: place.val(ev, rest, stack, goal) case GSkip{0n}: place.val(ev, rest, stack, goal)# a finished string. A key goes to the open object; anything else is a valuedef str.place(piece: Piece, rest: String, stack: List<&2, Frame>, goal: Goal) -> St: match stack: case Con{FObj{SKey{}, fresh}, frames}: place.key(str.ev(piece, True{}), rest, frames, goal) case other: str.val(str.ev(piece, False{}), rest, stack, goal)# a string a skip will not keepdef str.skip(rest: String, stack: List<&2, Frame>, goal: Goal) -> St: match goal: case GNext{}: err.st(goal) case GSkip{depth}: str.place(POwn{""}, rest, stack, goal)# a value has landed, and the cursor rest is the suffix already in handdef place.val.ok.here(ev: Ev, goal: Goal, stack: List<&2, Frame>, root: Bool) -> St: match goal: case GNext{}: S{MRest{SEv{ev}}, stack, GNext{}, root, False{}} case GSkip{0n}: S{MRest{SSkip{}}, stack, GSkip{0n}, root, False{}} case GSkip{1n+left}: S{MIdle{}, stack, GSkip{1n+left}, False{}, False{}}# a value has landed on the suffix in hand, once the slot is knowndef place.val.on.here(ev: Ev, goal: Goal, spot: Spot) -> St: match spot: case SpotBad{}: err.st(goal) case Spot{stack, root}: place.val.ok.here(ev, goal, stack, root)# a value has landed on the suffix in handdef place.val.here(ev: Ev, stack: List<&2, Frame>, goal: Goal) -> St: place.val.on.here(ev, goal, place.spot(stack))# an object key has landed on the suffix in handdef place.key.here(ev: Ev, frames: List<&2, Frame>, goal: Goal) -> St: match goal: case GNext{}: S{MRest{SEv{ev}}, FObj{SCol{}, False{}} <> frames, goal, False{}, False{}} case GSkip{0n}: err.st(goal) case GSkip{1n+left}: S{MIdle{}, FObj{SCol{}, False{}} <> frames, goal, False{}, False{}}# a string value on the suffix in hand. A skip inside a container keeps scanningdef str.val.here(ev: Ev, stack: List<&2, Frame>, goal: Goal) -> St: match goal: case GSkip{1n+left}: str.cont(stack, goal) case GNext{}: place.val.here(ev, stack, goal) case GSkip{0n}: place.val.here(ev, stack, goal)# a finished string whose rest is the suffix the stepper is ondef str.place.here(piece: Piece, stack: List<&2, Frame>, goal: Goal) -> St: match stack: case Con{FObj{SKey{}, fresh}, frames}: place.key.here(str.ev(piece, True{}), frames, goal) case other: str.val.here(str.ev(piece, False{}), stack, goal)# a skipped string closed on the suffix in handdef str.skip.here(stack: List<&2, Frame>, goal: Goal) -> St: match goal: case GNext{}: err.st(goal) case GSkip{depth}: str.place.here(POwn{""}, stack, goal)# one character of a string: stay in it, close it, start an escape, or faildef span.kind(cls: Lex.Class, ctrl: Bool) -> Skind: match cls ctrl: case Lex.CQuote{} c: KEnd{} case Lex.CBack{} False{}: KEsc{} case k True{}: KBad{} case k False{}: KGo{}# classify one string character. `ch` is a code point, so the keep is a worddef span.kind.of(+ch: Char) -> Skind: span.kind(Lex.classify(ch), U32.is_lt(Char.to_u32(ch), 32))# `]` or `}` of a skip: depth 1 finishes the value, deeper keeps going.# The cursor rest is the suffix the stepper is already ondef close.skip.go(stack: List<&2, Frame>, left: Nat, root: Bool) -> St: match left: case 0n: S{MRest{SSkip{}}, stack, GSkip{0n}, root, False{}} case 1n+more: S{MIdle{}, stack, GSkip{1n+more}, False{}, False{}}# `]` or `}` of a skip, once the frame has been poppeddef close.skip(stack: List<&2, Frame>, depth: Nat, root: Bool) -> St: match depth: case 0n: err.st(GSkip{0n}) case 1n+left: close.skip.go(stack, left, root)# the parent after a container closes, for a readerdef close.next(ev: Ev, stack: List<&2, Frame>, root: Bool) -> St: S{MRest{SEv{ev}}, stack, GNext{}, root, False{}}# a container closed. `frames` is the stack with that frame poppeddef close.out(ev: Ev, frames: List<&2, Frame>, goal: Goal) -> St: match frames goal: case Nil{} GNext{}: close.next(ev, Nil{}, True{}) case Nil{} GSkip{depth}: close.skip(Nil{}, depth, True{}) case Con{FArr{SVal{}, fresh}, outer} GNext{}: close.next(ev, FArr{SCom{}, False{}} <> outer, False{}) case Con{FObj{SVal{}, fresh}, outer} GNext{}: close.next(ev, FObj{SCom{}, False{}} <> outer, False{}) case Con{FArr{SVal{}, fresh}, outer} GSkip{depth}: close.skip(FArr{SCom{}, False{}} <> outer, depth, False{}) case Con{FObj{SVal{}, fresh}, outer} GSkip{depth}: close.skip(FObj{SCom{}, False{}} <> outer, depth, False{}) case frames2 g: err.st(g)# a `]`. Empty, or just after a valuedef close.arr(stack: List<&2, Frame>, goal: Goal) -> St: match stack: case Con{FArr{SVal{}, True{}}, frames}: close.out(EEndArr{}, frames, goal) case Con{FArr{SCom{}, fresh}, frames}: close.out(EEndArr{}, frames, goal) case other: err.st(goal)# a `}`. Empty, or just after a valuedef close.obj(stack: List<&2, Frame>, goal: Goal) -> St: match stack: case Con{FObj{SKey{}, True{}}, frames}: close.out(EEndObj{}, frames, goal) case Con{FObj{SCom{}, fresh}, frames}: close.out(EEndObj{}, frames, goal) case other: err.st(goal)# a comma between values, or between object membersdef punct.comma(stack: List<&2, Frame>, goal: Goal) -> St: match stack: case Con{FArr{SCom{}, fresh}, frames}: S{MIdle{}, FArr{SVal{}, False{}} <> frames, goal, False{}, False{}} case Con{FObj{SCom{}, fresh}, frames}: S{MIdle{}, FObj{SKey{}, False{}} <> frames, goal, False{}, False{}} case other: err.st(goal)# a colon after a keydef punct.colon(stack: List<&2, Frame>, goal: Goal) -> St: match stack: case Con{FObj{SCol{}, fresh}, frames}: S{MIdle{}, FObj{SVal{}, False{}} <> frames, goal, False{}, False{}} case other: err.st(goal)# `[` pushed. A reader returns the begin; a skip counts the containerdef punct.arr.go(stack: List<&2, Frame>, goal: Goal) -> St: match goal: case GNext{}: S{MRest{SEv{EBeginArr{}}}, stack, goal, False{}, False{}} case GSkip{depth}: S{MIdle{}, stack, GSkip{1n+depth}, False{}, False{}}# `[`, once it is known whether a value may startdef punct.arr.if(stack: List<&2, Frame>, goal: Goal, ok: Bool) -> St: match ok: case True{}: punct.arr.go(FArr{SVal{}, True{}} <> stack, goal) case False{}: err.st(goal)# `[`, when a value may startdef punct.arr(+stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: punct.arr.if(stack, goal, expect.val(stack, done))# `{` pusheddef punct.obj.go(stack: List<&2, Frame>, goal: Goal) -> St: match goal: case GNext{}: S{MRest{SEv{EBeginObj{}}}, stack, goal, False{}, False{}} case GSkip{depth}: S{MIdle{}, stack, GSkip{1n+depth}, False{}, False{}}# `{`, once it is known whether a value may startdef punct.obj.if(stack: List<&2, Frame>, goal: Goal, ok: Bool) -> St: match ok: case True{}: punct.obj.go(FObj{SKey{}, True{}} <> stack, goal) case False{}: err.st(goal)# `{`, when a value may startdef punct.obj(+stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: punct.obj.if(stack, goal, expect.val(stack, done))# one punctuation token, once the root is still open. The suffix stays# with the stepper: a bracket closes on it, and a comma does not keep itdef punct.go(tok: Lex.Tok, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: match tok: case Lex.TOpenArr{}: punct.arr(stack, goal, done) case Lex.TOpenObj{}: punct.obj(stack, goal, done) case Lex.TCloseArr{}: close.arr(stack, goal) case Lex.TCloseObj{}: close.obj(stack, goal) case Lex.TColon{}: punct.colon(stack, goal) case Lex.TComma{}: punct.comma(stack, goal) case other: err.st(goal)# one punctuation tokendef punct.on(tok: Lex.Tok, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: match done: case True{}: err.st(goal) case False{}: punct.go(tok, stack, goal, done)# whitespace, kept as the same statedef idle.space(stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: S{MIdle{}, stack, goal, done, False{}}# a `"` may open a string heredef look.quote(stack: List<&2, Frame>, goal: Goal, done: Bool, ok: Bool) -> St: match ok: case True{}: S{MQuote{}, stack, goal, done, False{}} case False{}: err.st(goal)# a word's first character. The buffer is that character, not the array behind itdef look.word(+ch: Char, stack: List<&2, Frame>, goal: Goal, done: Bool, ok: Bool) -> St: match ok: case True{}: S{MWord{ch <> [], 1, V.num.ok.step(V.NStart{}, Char.to_u32(ch))}, stack, goal, done, False{}} case False{}: err.st(goal)# one character between tokens. The suffix stays with the stepper: a bracket# closes on it, a comma does not keep it, and a word keeps only its own chardef look.plan( ch: Char, +stack: List<&2, Frame>, +goal: Goal, +done: Bool, cls: Lex.Class) -> St: match cls: case Lex.CSpace{}: idle.space(stack, goal, done) case Lex.CPunct{tok}: punct.on(tok, stack, goal, done) case Lex.CQuote{}: look.quote(stack, goal, done, expect.str.for(stack, goal, done)) case Lex.CBack{}: err.st(goal) case Lex.COther{}: look.word(ch, stack, goal, done, expect.val(stack, done))# the end of the text, between tokensdef idle.eof(stack: List<&2, Frame>, goal: Goal, done: Bool, bad: Bool) -> (Stop & Cur): match goal done bad: case GNext{} True{} False{}: (SEv{EEnd{}}, Cur{"", stack, True{}, False{}}) case g d b: bad.cur()# a number, when the word is not null, true, or falsedef word.kind.num(ok: Bool) -> Atom: match ok: case True{}: ANum{} case False{}: ABad{}# null, true, false, or a number that kept a legal spellingdef word.kind.go(num: V.Num, is_null: Bool, is_true: Bool, is_false: Bool) -> Atom: match is_null is_true is_false: case True{} x y: ANull{} case False{} True{} y: ATrue{} case False{} False{} True{}: AFalse{} case False{} False{} False{}: word.kind.num(V.num.ok.done(num))# what a bare word isdef word.kind(+cut: String, +nn: U32, num: V.Num) -> Atom: word.kind.go(num, V.span.eq(cut, nn, "null"), V.span.eq(cut, nn, "true"), V.span.eq(cut, nn, "false"))# a word that ended at the end of the textdef word.eof.place( atom: Atom, cut: String, nn: U32, stack: List<&2, Frame>, goal: Goal) -> St: match atom: case ABad{}: err.st(goal) case ANull{}: place.val(ENull{}, "", stack, goal) case ATrue{}: place.val(EBool{True{}}, "", stack, goal) case AFalse{}: place.val(EBool{False{}}, "", stack, goal) case ANum{}: place.val(ENum{cut, nn}, "", stack, goal)# the delimiter of a skipped word, once the value is inside the container.# The delimiter is consumed. The caller resumes on the suffix after itdef lay.plan(stack: List<&2, Frame>, goal: Goal, cls: Lex.Class) -> St: match cls: case Lex.CSpace{}: S{MIdle{}, stack, goal, False{}, False{}} case Lex.CPunct{tok}: punct.on(tok, stack, goal, False{}) case other: err.st(goal)# place the word, then take the delimiter, because the skip is still opendef see.more.on(goal: Goal, cls: Lex.Class, spot: Spot) -> St: match spot: case SpotBad{}: err.st(goal) case Spot{stack, root}: lay.plan(stack, goal, cls)# place the word, then take the delimiterdef see.more(stack: List<&2, Frame>, goal: Goal, cls: Lex.Class) -> St: see.more.on(goal, cls, place.spot(stack))# a code point that ends a bare word: the same bytes `classify` calls# punctuation, a quote, a backslash, or whitespace. A form feed stays in the worddef word.special(+cp: U32) -> Bool: Bool.or(U32.is_eq(cp, 9), Bool.or(U32.is_eq(cp, 10), Bool.or(U32.is_eq(cp, 13), Bool.or(U32.is_eq(cp, 32), Bool.or(U32.is_eq(cp, 34), Bool.or(U32.is_eq(cp, 44), Bool.or(U32.is_eq(cp, 58), Bool.or(U32.is_eq(cp, 91), Bool.or(U32.is_eq(cp, 92), Bool.or(U32.is_eq(cp, 93), Bool.or(U32.is_eq(cp, 123), U32.is_eq(cp, 125))))))))))))# where a plain word run stopped. A stop keeps the delimiter, already consumedtype Wst is Data: WMore{buf: List<&2, Char>, nn: U32, num: V.Num} WStop{buf: List<&2, Char>, nn: U32, num: V.Num, ch: Char} WEof{buf: List<&2, Char>, nn: U32, num: V.Num}# one more plain byte of a word. The buffer grows by that characterdef word.step(ch: Char, +cp: U32, buf: List<&2, Char>, nn: U32, num: V.Num) -> Wst: WMore{ch <> buf, U32.add(nn, 1), V.num.ok.step(num, cp)}# keep the byte when it is not a delimiterdef word.keep.go( ch: Char, cp: U32, buf: List<&2, Char>, nn: U32, num: V.Num, keep: Bool) -> Wst: match keep: case True{}: word.step(ch, cp, buf, nn, num) case False{}: WStop{buf, nn, num, ch}# a digit is never a delimiter, so a long number does not walk `classify`def word.digit.go( ch: Char, +cp: U32, buf: List<&2, Char>, nn: U32, num: V.Num, dig: Bool) -> Wst: match dig: case True{}: word.step(ch, cp, buf, nn, num) case False{}: word.keep.go(ch, cp, buf, nn, num, Bool.not(word.special(cp)))# one byte of a word: another plain byte, or the delimiter that ends itdef word.decide(ch: Char, +cp: U32, buf: List<&2, Char>, nn: U32, num: V.Num) -> Wst: word.digit.go(ch, cp, buf, nn, num, V.num.ok.digit(cp))# the suffix after a plain word. Tail call, so a long number is one loopdef word.run(txt: String, st: Wst) -> (String & Wst): match txt st: case rest WStop{buf, nn, num, ch}: (rest, WStop{buf, nn, num, ch}) case rest WEof{buf, nn, num}: (rest, WEof{buf, nn, num}) case SNil{} WMore{buf, nn, num}: ("", WEof{buf, nn, num}) case SCon{+c, t} WMore{buf, nn, num}: word.run(t, word.decide(c, Char.to_u32(c), buf, nn, num))# drop an unread suffix. Tail call, so a failed long value does not stackdef word.drop(txt: String) -> Bool: match txt: case SNil{}: True{} case SCon{c, t}: word.drop(t)# drop a word's characters. Tail call, so a long spelling does not stackdef word.forget(buf: List<&2, Char>) -> Bool: match buf: case Nil{}: True{} case Con{c, t}: word.forget(t)# a failed word, once its buffer and the unread suffix are gonedef word.burst.err(goal: Goal, dropped: Bool) -> St: match dropped: case d: err.st(goal)# a failed word drops the buffer after the unread suffixdef word.burst.dead(goal: Goal, dropped: Bool, buf: List<&2, Char>) -> St: match dropped: case d: word.burst.err(goal, word.forget(buf))# a finished event already carries its suffix. Anything else continues on `rest`def jump.keep(dropped: Bool, st: St) -> St: match dropped st: case d S{mode, stack, goal, done, bad}: S{mode, stack, goal, done, bad}# a state that already stopped drops `rest`. Anything else resumes on itdef jump.unless.done(+rest: String, st: St) -> St: match st: case S{MDone{stop, saved}, stack, goal, done, bad}: jump.keep(word.drop(rest), S{MDone{stop, saved}, stack, goal, done, bad}) case S{mode, stack, goal, done, bad}: S{MJump{rest, mode}, stack, goal, done, bad}# a word that ended because the text did, once its spelling is in handdef word.burst.eof.raw( +raw: String, +nn: U32, num: V.Num, stack: List<&2, Frame>, goal: Goal) -> St: match goal: case GSkip{1n+left}: word.burst.err(goal, word.drop(raw)) case GNext{}: word.eof.place(word.kind(raw, nn, num), raw, nn, stack, goal) case GSkip{0n}: word.eof.place(word.kind(raw, nn, num), raw, nn, stack, goal)# the empty suffix of an eof is dropped, then the word is placeddef word.burst.eof.at( dropped: Bool, buf: List<&2, Char>, nn: U32, num: V.Num, stack: List<&2, Frame>, goal: Goal) -> St: match dropped: case d: word.burst.eof.raw(Lex.text(buf), nn, num, stack, goal)# the skip is still inside a container, so the delimiter is taken and the# stepper continues. Only this path classifies that bytedef word.burst.skip(rest: String, +ch: Char, stack: List<&2, Frame>, goal: Goal) -> St: jump.unless.done(rest, see.more(stack, goal, Lex.classify(ch)))# a value landed on the delimiter. The suffix is consed back once, not copied.# The number keeps its own spelling, so it does not hold the rest of the arraydef word.burst.goal( ev: Ev, ch: Char, rest: String, stack: List<&2, Frame>, goal: Goal) -> St: match goal: case GNext{}: place.val(ev, SCon{ch, rest}, stack, goal) case GSkip{0n}: place.val(ev, SCon{ch, rest}, stack, goal) case GSkip{1n+left}: word.burst.skip(rest, ch, stack, goal)# both the spelling and the unread suffix are dropped when the word is not onedef word.burst.junk(goal: Goal, dropped: Bool, rest: String) -> St: match dropped: case d: word.burst.err(goal, word.drop(rest))# place the word. A number keeps its own spelling; the delimiter stays unreaddef word.burst.atom( rest: String, ch: Char, raw: String, nn: U32, stack: List<&2, Frame>, goal: Goal, atom: Atom) -> St: match atom: case ABad{}: word.burst.junk(goal, word.drop(raw), SCon{ch, rest}) case ANull{}: word.burst.goal(ENull{}, ch, rest, stack, goal) case ATrue{}: word.burst.goal(EBool{True{}}, ch, rest, stack, goal) case AFalse{}: word.burst.goal(EBool{False{}}, ch, rest, stack, goal) case ANum{}: word.burst.goal(ENum{raw, nn}, ch, rest, stack, goal)# the delimiter is classified once, and only when a skip must consume it.# The plain bytes never weredef word.burst.raw( rest: String, ch: Char, +raw: String, +nn: U32, num: V.Num, stack: List<&2, Frame>, goal: Goal) -> St: word.burst.atom(rest, ch, raw, nn, stack, goal, word.kind(raw, nn, num))# a word ended on a delimiter. Its spelling is its own textdef word.burst.stop( rest: String, ch: Char, buf: List<&2, Char>, nn: U32, num: V.Num, stack: List<&2, Frame>, goal: Goal) -> St: word.burst.raw(rest, ch, Lex.text(buf), nn, num, stack, goal)# both the unread suffix and the buffer are dropped when the word cannot finishdef word.burst.fail(goal: Goal, dropped: Bool, buf: List<&2, Char>) -> St: match dropped: case d: word.burst.dead(goal, True{}, buf)# `got` is the suffix after the run, and why it stoppeddef word.burst.at(got: (String & Wst), stack: List<&2, Frame>, goal: Goal) -> St: (rest, wst) = got match wst: case WEof{buf, nn, num}: word.burst.eof.at(word.drop(rest), buf, nn, num, stack, goal) case WStop{buf, nn, num, ch}: word.burst.stop(rest, ch, buf, nn, num, stack, goal) case WMore{buf, nn, num}: word.burst.fail(goal, word.drop(rest), buf)# plain digits and letters in one loop. The delimiter is consed back oncedef word.burst( txt: String, buf: List<&2, Char>, nn: U32, num: V.Num, stack: List<&2, Frame>, goal: Goal, bad: Bool) -> St: match bad: case True{}: word.burst.dead(goal, word.drop(txt), buf) case False{}: word.burst.at(word.run(txt, WMore{buf, nn, num}), stack, goal)# a backslash in a span: copy the prefix, or drop it when skippingdef span.esc( +cut: String, +nn: U32, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: match goal: case GNext{}: S{MEsc{copy.rev(cut, nn, U32.is_zero(nn), [])}, stack, goal, done, False{}} case GSkip{depth}: S{MSkEsc{}, stack, goal, done, False{}}# the next state after one character of a span. The unread tail stays with# the stepper, so the usual character does not keep itdef span.advance( key: Skind, cut: String, nn: U32, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: match key: case KGo{}: S{MSpan{cut, U32.add(nn, 1)}, stack, goal, done, False{}} case KEnd{}: str.place.here(PSpan{cut, nn}, stack, goal) case KEsc{}: span.esc(cut, nn, stack, goal, done) case KBad{}: err.st(goal)# the first character inside a string. A normal one opens the span on this# suffix; the stepper then walks the tail, so the keep is once per stringdef quote.go( key: Skind, ch: Char, tl: String, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: match key: case KGo{}: S{MSpan{SCon{ch, tl}, 1}, stack, goal, done, False{}} case KEnd{}: str.place.here(PSpan{"", 0}, stack, goal) case KEsc{}: span.esc("", 0, stack, goal, done) case KBad{}: err.st(goal)# the first character inside a stringdef quote.first( +ch: Char, tl: String, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: quote.go(span.kind.of(ch), ch, tl, stack, goal, done)# the next state after one character of a string being skippeddef sk.advance(key: Skind, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: match key: case KGo{}: S{MSkStr{}, stack, goal, done, False{}} case KEnd{}: str.skip.here(stack, goal) case KEsc{}: S{MSkEsc{}, stack, goal, done, False{}} case KBad{}: err.st(goal)# one character of a string that is being decodeddef copy.hit.go( +ch: Char, +tl: String, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, cls: Lex.Class, ctrl: Bool) -> St: match cls ctrl: case Lex.CQuote{} c: str.place(POwn{Lex.text(buf)}, tl, stack, goal) case Lex.CBack{} False{}: S{MEsc{buf}, stack, goal, done, False{}} case k True{}: err.st(goal) case k False{}: S{MCopy{ch <> buf}, stack, goal, done, False{}}# one character of a string that is being decodeddef copy.hit( +ch: Char, +tl: String, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, bad: Bool, cls: Lex.Class, ctrl: Bool) -> St: match bad: case True{}: err.st(goal) case False{}: copy.hit.go(ch, tl, buf, stack, goal, done, cls, ctrl)# the character after a backslash, once `u` has been noticeddef esc.put( _tl: String, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, got: Maybe<&2, Char>) -> St: match got: case Some{ch}: S{MCopy{ch <> buf}, stack, goal, done, False{}} case None{}: err.st(goal)# the character after a backslashdef esc.hit.go( +ch: Char, +tl: String, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, is_u: Bool) -> St: match is_u: case True{}: S{MUni{3n, 0, buf}, stack, goal, done, False{}} case False{}: esc.put(tl, buf, stack, goal, done, Lex.unescape(ch))# the character after a backslashdef esc.hit( +ch: Char, +tl: String, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, bad: Bool) -> St: match bad: case True{}: err.st(goal) case False{}: esc.hit.go(ch, tl, buf, stack, goal, done, U32.is_eq(Char.to_u32(ch), 117))# a `\u` code point that is not a surrogatedef uni.end.go( _tl: String, cp: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, sur: Lex.Sur) -> St: match sur: case Lex.SOk{}: S{MCopy{Char.from_u32(cp) <> buf}, stack, goal, done, False{}} case Lex.SHi{}: S{MHi{cp, buf}, stack, goal, done, False{}} case Lex.SLo{}: err.st(goal)# four hex digits are indef uni.end( tl: String, +cp: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: uni.end.go(tl, cp, buf, stack, goal, done, Lex.unicode.sur(cp))# one more hex digit, or the code pointdef uni.put( tl: String, left: Nat, acc: U32, dig: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: match left: case 0n: uni.end(tl, (acc * 16 + dig : U32), buf, stack, goal, done) case 1n+p: S{MUni{p, (acc * 16 + dig : U32), buf}, stack, goal, done, False{}}# one hex digit of a `\u` escapedef uni.hit.go( +ch: Char, +tl: String, left: Nat, acc: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, ok: Bool) -> St: match ok: case True{}: uni.put(tl, left, acc, Lex.hex(ch), buf, stack, goal, done) case False{}: err.st(goal)# one hex digit of a `\u` escapedef uni.hit( +ch: Char, +tl: String, left: Nat, acc: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, bad: Bool) -> St: match bad: case True{}: err.st(goal) case False{}: uni.hit.go(ch, tl, left, acc, buf, stack, goal, done, Lex.hex.ok(Char.to_u32(ch)))# the character after a high surrogate, on a live cursordef hi.hit.go( _tl: String, hi: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, cls: Lex.Class) -> St: match cls: case Lex.CBack{}: S{MHiEsc{hi, buf}, stack, goal, done, False{}} case other: err.st(goal)# the character after a high surrogate: it has to be a backslashdef hi.hit( tl: String, hi: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, bad: Bool, cls: Lex.Class) -> St: match bad: case True{}: err.st(goal) case False{}: hi.hit.go(tl, hi, buf, stack, goal, done, cls)# the character after the backslash of a pair: it has to be `u`def hiesc.hit.go( _tl: String, hi: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, is_u: Bool) -> St: match is_u: case True{}: S{MLo{3n, 0, hi, buf}, stack, goal, done, False{}} case False{}: err.st(goal)# the character after the backslash of a pairdef hiesc.hit( +ch: Char, tl: String, hi: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, bad: Bool) -> St: match bad: case True{}: err.st(goal) case False{}: hiesc.hit.go(tl, hi, buf, stack, goal, done, U32.is_eq(Char.to_u32(ch), 117))# the low surrogate is in, or it is not onedef lo.end.go( _tl: String, lo: U32, hi: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, sur: Lex.Sur) -> St: match sur: case Lex.SLo{}: S{MCopy{Char.from_u32(Lex.unicode.pair(hi, lo)) <> buf}, stack, goal, done, False{}} case other: err.st(goal)# four hex digits of the low surrogate are indef lo.end( tl: String, +lo: U32, hi: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: lo.end.go(tl, lo, hi, buf, stack, goal, done, Lex.unicode.sur(lo))# one more hex digit of the low surrogate, or the code pointdef lo.put( tl: String, left: Nat, acc: U32, hi: U32, dig: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: match left: case 0n: lo.end(tl, (acc * 16 + dig : U32), hi, buf, stack, goal, done) case 1n+p: S{MLo{p, (acc * 16 + dig : U32), hi, buf}, stack, goal, done, False{}}# one hex digit of the low surrogatedef lo.hit.go( +ch: Char, +tl: String, left: Nat, acc: U32, hi: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, ok: Bool) -> St: match ok: case True{}: lo.put(tl, left, acc, hi, Lex.hex(ch), buf, stack, goal, done) case False{}: err.st(goal)# one hex digit of the low surrogatedef lo.hit( +ch: Char, +tl: String, left: Nat, acc: U32, hi: U32, buf: List<&2, Char>, stack: List<&2, Frame>, goal: Goal, done: Bool, bad: Bool) -> St: match bad: case True{}: err.st(goal) case False{}: lo.hit.go(ch, tl, left, acc, hi, buf, stack, goal, done, Lex.hex.ok(Char.to_u32(ch)))# a decoded short escape puts the skip back in the stringdef sk.esc.put( _tl: String, stack: List<&2, Frame>, goal: Goal, done: Bool, got: Maybe<&2, Char>) -> St: match got: case Some{ch}: S{MSkStr{}, stack, goal, done, False{}} case None{}: err.st(goal)# a short escape, or the start of `\u`, inside a skipped stringdef sk.esc.go( +ch: Char, tl: String, stack: List<&2, Frame>, goal: Goal, done: Bool, is_u: Bool) -> St: match is_u: case True{}: S{MSkUni{3n, 0}, stack, goal, done, False{}} case False{}: sk.esc.put(tl, stack, goal, done, Lex.unescape(ch))# the character after a backslash in a skipped stringdef sk.esc( +ch: Char, tl: String, stack: List<&2, Frame>, goal: Goal, done: Bool, bad: Bool) -> St: match bad: case True{}: err.st(goal) case False{}: sk.esc.go(ch, tl, stack, goal, done, U32.is_eq(Char.to_u32(ch), 117))# a skipped `\u` that is not a surrogatedef sk.uni.end.go( _tl: String, stack: List<&2, Frame>, goal: Goal, done: Bool, sur: Lex.Sur, cp: U32) -> St: match sur: case Lex.SOk{}: S{MSkStr{}, stack, goal, done, False{}} case Lex.SHi{}: S{MSkHi{cp}, stack, goal, done, False{}} case Lex.SLo{}: err.st(goal)# four hex digits of a skipped `\u` are indef sk.uni.end( tl: String, +cp: U32, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: sk.uni.end.go(tl, stack, goal, done, Lex.unicode.sur(cp), cp)# one more skipped hex digit, or the code pointdef sk.uni.put( tl: String, left: Nat, acc: U32, dig: U32, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: match left: case 0n: sk.uni.end(tl, (acc * 16 + dig : U32), stack, goal, done) case 1n+p: S{MSkUni{p, (acc * 16 + dig : U32)}, stack, goal, done, False{}}# one hex digit of a skipped `\u`def sk.uni.go( +ch: Char, tl: String, left: Nat, acc: U32, stack: List<&2, Frame>, goal: Goal, done: Bool, ok: Bool) -> St: match ok: case True{}: sk.uni.put(tl, left, acc, Lex.hex(ch), stack, goal, done) case False{}: err.st(goal)# one hex digit of a skipped `\u`def sk.uni( +ch: Char, tl: String, left: Nat, acc: U32, stack: List<&2, Frame>, goal: Goal, done: Bool, bad: Bool) -> St: match bad: case True{}: err.st(goal) case False{}: sk.uni.go(ch, tl, left, acc, stack, goal, done, Lex.hex.ok(Char.to_u32(ch)))# the character after a skipped high surrogatedef sk.hi(stack: List<&2, Frame>, goal: Goal, done: Bool, hi: U32, cls: Lex.Class) -> St: match cls: case Lex.CBack{}: S{MSkHiEsc{hi}, stack, goal, done, False{}} case other: err.st(goal)# `u` after the backslash of a skipped pairdef sk.hiesc(stack: List<&2, Frame>, goal: Goal, done: Bool, hi: U32, is_u: Bool) -> St: match is_u: case True{}: S{MSkLo{3n, 0, hi}, stack, goal, done, False{}} case False{}: err.st(goal)# the low half of a skipped pair is in, or it is not onedef sk.lo.end(_tl: String, stack: List<&2, Frame>, goal: Goal, done: Bool, sur: Lex.Sur) -> St: match sur: case Lex.SLo{}: S{MSkStr{}, stack, goal, done, False{}} case other: err.st(goal)# one more hex digit of a skipped low surrogate, or the code pointdef sk.lo.put( tl: String, left: Nat, acc: U32, hi: U32, dig: U32, stack: List<&2, Frame>, goal: Goal, done: Bool) -> St: match left: case 0n: sk.lo.end(tl, stack, goal, done, Lex.unicode.sur((acc * 16 + dig : U32))) case 1n+p: S{MSkLo{p, (acc * 16 + dig : U32), hi}, stack, goal, done, False{}}# one hex digit of a skipped low surrogatedef sk.lo.go( +ch: Char, tl: String, left: Nat, acc: U32, hi: U32, stack: List<&2, Frame>, goal: Goal, done: Bool, ok: Bool) -> St: match ok: case True{}: sk.lo.put(tl, left, acc, hi, Lex.hex(ch), stack, goal, done) case False{}: err.st(goal)# the levels of the walk's budget: next.grow runs levels 0 to 32 of leaves of# next.leaf() jumps, so a walk may jump back from a plain word 4 (2^33 - 1)# times, more than the 2^32 - 1 it had. A byte step keeps the count# and shrinks the suffix instead, so a long string does not burn it. The# levels, not the count, are the literal, so a proof about `next` never# expands four billion successorsdef next.levels() -> Nat: 33n# one character, between events. A plain word is one tail loop. A finished# event returns; everything else tail-calls on the shorter suffix. A string# character and a bracket are not handed to the step twice, so a long row or# a long array moves the suffix instead of keeping itdef next.go(fuel: Nat, txt: String, st: St) -> Out: match fuel txt st: case n any S{MDone{stop, saved}, stack, goal, done, bad}: Fin{(stop, Cur{saved, stack, done, bad})} case n txt S{MRest{stop}, stack, goal, done, bad}: Fin{(stop, Cur{txt, stack, done, bad})} case 0n any S{mode, stack, goal, done, bad}: More{any, S{mode, stack, goal, done, bad}} case 1n+left any S{MJump{rest, mode}, stack, goal, done, bad}: next.go(left, rest, S{mode, stack, goal, done, bad}) case 1n+left SNil{} S{MQuote{}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{ch, tl} S{MQuote{}, stack, goal, done, True{}}: next.go(1n+left, tl, err.st(goal)) case 1n+left SCon{ch, +tl} S{MQuote{}, stack, goal, done, False{}}: next.go(1n+left, tl, quote.first(ch, tl, stack, goal, done)) case 1n+left SNil{} S{MIdle{}, stack, goal, done, bad}: Fin{idle.eof(stack, goal, done, bad)} case 1n+left SCon{ch, tl} S{MIdle{}, stack, goal, done, True{}}: next.go(1n+left, tl, err.st(goal)) case 1n+left SCon{ch, tl} S{MIdle{}, stack, goal, done, False{}}: next.go(1n+left, tl, look.plan(ch, stack, goal, done, Lex.classify(ch))) case 1n+left txt S{MWord{buf, nn, num}, stack, goal, done, bad}: next.go(left, "", word.burst(txt, buf, nn, num, stack, goal, bad)) case 1n+left SNil{} S{MSpan{cut, nn}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{ch, tl} S{MSpan{cut, nn}, stack, goal, done, True{}}: next.go(1n+left, tl, err.st(goal)) case 1n+left SCon{ch, tl} S{MSpan{cut, nn}, stack, goal, done, False{}}: next.go(1n+left, tl, span.advance(span.kind.of(ch), cut, nn, stack, goal, done)) case 1n+left SNil{} S{MCopy{buf}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{+ch, +tl} S{MCopy{buf}, stack, goal, done, bad}: next.go(1n+left, tl, copy.hit(ch, tl, buf, stack, goal, done, bad, Lex.classify(ch), U32.is_lt(Char.to_u32(ch), 32))) case 1n+left SNil{} S{MEsc{buf}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{+ch, +tl} S{MEsc{buf}, stack, goal, done, bad}: next.go(1n+left, tl, esc.hit(ch, tl, buf, stack, goal, done, bad)) case 1n+left SNil{} S{MUni{left2, acc, buf}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{+ch, +tl} S{MUni{left2, acc, buf}, stack, goal, done, bad}: next.go(1n+left, tl, uni.hit(ch, tl, left2, acc, buf, stack, goal, done, bad)) case 1n+left SNil{} S{MHi{hi, buf}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{ch, tl} S{MHi{hi, buf}, stack, goal, done, bad}: next.go(1n+left, tl, hi.hit(tl, hi, buf, stack, goal, done, bad, Lex.classify(ch))) case 1n+left SNil{} S{MHiEsc{hi, buf}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{+ch, tl} S{MHiEsc{hi, buf}, stack, goal, done, bad}: next.go(1n+left, tl, hiesc.hit(ch, tl, hi, buf, stack, goal, done, bad)) case 1n+left SNil{} S{MLo{left2, acc, hi, buf}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{+ch, +tl} S{MLo{left2, acc, hi, buf}, stack, goal, done, bad}: next.go(1n+left, tl, lo.hit(ch, tl, left2, acc, hi, buf, stack, goal, done, bad)) case 1n+left SNil{} S{MSkStr{}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{ch, tl} S{MSkStr{}, stack, goal, done, True{}}: next.go(1n+left, tl, err.st(goal)) case 1n+left SCon{ch, tl} S{MSkStr{}, stack, goal, done, False{}}: next.go(1n+left, tl, sk.advance(span.kind.of(ch), stack, goal, done)) case 1n+left SNil{} S{MSkEsc{}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{+ch, tl} S{MSkEsc{}, stack, goal, done, bad}: next.go(1n+left, tl, sk.esc(ch, tl, stack, goal, done, bad)) case 1n+left SNil{} S{MSkUni{left2, acc}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{+ch, tl} S{MSkUni{left2, acc}, stack, goal, done, bad}: next.go(1n+left, tl, sk.uni(ch, tl, left2, acc, stack, goal, done, bad)) case 1n+left SNil{} S{MSkHi{hi}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{ch, tl} S{MSkHi{hi}, stack, goal, done, bad}: next.go(1n+left, tl, sk.hi(stack, goal, done, hi, Lex.classify(ch))) case 1n+left SNil{} S{MSkHiEsc{hi}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{+ch, tl} S{MSkHiEsc{hi}, stack, goal, done, bad}: next.go(1n+left, tl, sk.hiesc(stack, goal, done, hi, U32.is_eq(Char.to_u32(ch), 117))) case 1n+left SNil{} S{MSkLo{left2, acc, hi}, stack, goal, done, bad}: Fin{bad.cur()} case 1n+left SCon{+ch, tl} S{MSkLo{left2, acc, hi}, stack, goal, done, bad}: next.go(1n+left, tl, sk.lo.go(ch, tl, left2, acc, hi, stack, goal, done, Lex.hex.ok(Char.to_u32(ch))))# the jumps one leaf of the budget allows. A plain word takes two (the burst# and the jump back), so most events finish in one leaf. A leaf that runs out# is not cut short: next.run resumes it in the next leafdef next.leaf() -> Nat: 4n# a walk with a budget of 2^lvl jumps: one step's budget at depth 0, and# the budget below it twice otherwise. A finished step passes throughdef next.run(lvl: Nat, res: Out) -> Out: match lvl res: case 0n More{txt, st}: next.go(next.leaf(), txt, st) case 1n+(+pp) More{txt, st}: next.run(pp, next.run(pp, More{txt, st})) case _ Fin{got}: Fin{got}# a walk that runs level `lvl`, then the next level up, until it finishes or# `left` levels are done. Most events finish at level 0, one step's budget, so# a walk pays for the levels only when it jumps many timesdef next.grow(left: Nat, +lvl: Nat, res: Out) -> Out: match left res: case 1n+pp More{txt, st}: next.grow(pp, 1n+lvl, next.run(lvl, More{txt, st})) case _ More{txt, st}: More{txt, st} case _ Fin{got}: Fin{got}# a step's answer. A budget used up is a failure, as it always wasdef next.fin(res: Out) -> (Stop & Cur): match res: case Fin{got}: got case More{_, _}: bad.cur()# a walk from the start of `txt` in state `st`, with the whole budgetdef next.walk(txt: String, st: St) -> (Stop & Cur): next.fin(next.grow(next.levels(), 0n, More{txt, st}))# the cursor after an end: nothing left to read, and the root is overdef next.ended(cur: Cur) -> Cur: Cur{_, stack, _, _} = cur Cur{"", stack, True{}, False{}}# an event from a step. A skip that surfaces here is a broken reader. An# error always comes with the failed cursor, and an end with an ended one, so# both stick: the steps build them that way, and this says so where it is useddef next.use(got: (Stop & Cur)) -> (Ev & Cur): (stop, cur) = got match stop: case SEv{EErr{}}: (EErr{}, Cur{"", Nil{}, False{}, True{}}) case SEv{EEnd{}}: (EEnd{}, next.ended(cur)) case SEv{ev}: (ev, cur) case SSkip{}: (EErr{}, Cur{"", Nil{}, False{}, True{}})# the next event, unless the cursor has already faileddef next.open(rest: String, stack: List<&2, Frame>, done: Bool, bad: Bool) -> (Ev & Cur): match bad: case True{}: (EErr{}, Cur{"", Nil{}, False{}, True{}}) case False{}: next.use(next.walk(rest, S{MIdle{}, stack, GNext{}, done, False{}}))# the cursor after a skip. An event other than a failure means the skip# stopped on something that was not one valuedef skip.use(got: (Stop & Cur)) -> Cur: (stop, cur) = got match stop: case SSkip{}: cur case SEv{EErr{}}: cur case SEv{ev}: Cur{"", Nil{}, False{}, True{}}# drop one value, unless the cursor has already failed or the root is overdef skip.open(rest: String, stack: List<&2, Frame>, done: Bool, bad: Bool) -> Cur: match done bad: case d True{}: Cur{"", Nil{}, False{}, True{}} case True{} False{}: Cur{"", Nil{}, False{}, True{}} case False{} False{}: skip.use(next.walk(rest, S{MIdle{}, stack, GSkip{0n}, False{}, False{}}))# a cursor at the start of `src`. It holds that string as its unread suffix,# so dropping the caller's own variable does not drop the cursor, and# dropping the cursor drops what it has not read yetdef cursor(src: String) -> Cur: Cur{src, Nil{}, False{}, False{}}# the next event. A comma or a colon is consumed on the way to the event# after it. After the root value, further calls are the end, or an error# when another value followsdef next(cur: Cur) -> (Ev & Cur): Cur{rest, stack, done, bad} = cur next.open(rest, stack, done, bad)# drop the next value: one scalar, or one array or object with everything# inside it. The cursor is left where that value ended. A string with no# escapes is not copied; a string being skipped is not decoded into a bufferdef skip(cur: Cur) -> Cur: Cur{rest, stack, done, bad} = cur skip.open(rest, stack, done, bad)# the text of a string, a key, or a number. A span is copied here. None when# the event is not one of thosedef text(ev: Ev) -> Maybe<&2, String>: match ev: case EStr{body}: Some{body} case EKey{body}: Some{body} case EStrS{src, +nn}: Some{V.span.str(src, nn, U32.is_zero(nn))} case EKeyS{src, +nn}: Some{V.span.str(src, nn, U32.is_zero(nn))} case ENum{src, +nn}: Some{V.span.str(src, nn, U32.is_zero(nn))} case other: None{}