import Base import ./types.bend as T import 0xd684886d10b431b9dce6c3b2d1ef1980/main.bend as V # Buffer and helpers are representation-private by API convention. type Source is Type: Buffer{file: U32, size: U32, chars: V.Vec, starts: V.Vec} def Source.maximum() -> U32: 16777215 def scalar(+c: Char) -> Bool: +u = Char.to_u32(c) Bool.and(U32.is_le(u, 1114111), Bool.or(U32.is_lt(u, 55296), U32.is_gt(u, 57343))) def finish_push(id: U32, n: U32, chars: V.Vec, lines: V.Vec, r: Result) -> Result: match r: case Fail{e}: Fail{T.Limit{}} case Done{x}: Done{Buffer{id,n,chars,lines}} def finish_line(id: U32, n: U32, chars: V.Vec, pair: V.Vec & Result) -> Result: (lines,r) = pair finish_push(id,n,chars,lines,r) def add_line(id: U32, +n: U32, chars: V.Vec, lines: V.Vec, newline: Bool) -> Result: match newline: case False{}: Done{Buffer{id,n,chars,lines}} case True{}: finish_line(id,n,chars,V.Vec.push(U32,lines,n)) def add_char_result(id: U32, n: U32, chars: V.Vec, lines: V.Vec, newline: Bool, r: Result) -> Result: match r: case Fail{e}: Fail{T.Limit{}} case Done{x}: add_line(id,n,chars,lines,newline) def finish_char(id: U32, n: U32, lines: V.Vec, newline: Bool, pair: V.Vec & Result) -> Result: (chars,r) = pair add_char_result(id,n,chars,lines,newline,r) def add_valid(s: Source, +c: Char, valid: Bool) -> Result: match s valid: case Buffer{id,n,chars,lines} False{}: Fail{T.InvalidScalar{}} case Buffer{id,n,chars,lines} True{}: finish_char(id,(n + 1 : U32),lines,Char.is_eq(c,'\n'),V.Vec.push(Char,chars,c)) def add(s: Result, +c: Char) -> Result: match s: case Fail{e}: Fail{e} case Done{src}: add_valid(src,c,scalar(c)) def build(text: String, s: Result) -> Result: match text: case SNil{}: s case SCon{c,tail}: build(tail,add(s,c)) def Source.bounded(file: U32, text: String, limit: U32) -> Result: build(text,finish_line(file,0,V.Vec.bounded(Char,U32.min(limit,Source.maximum())),V.Vec.push(U32,V.Vec.new(U32),0))) def Source.new(file: U32, text: String) -> Result: Source.bounded(file,text,Source.maximum()) def Source.file(s: Source) -> Source & U32: match s: case Buffer{+id,n,chars,lines}: (Buffer{id,n,chars,lines},id) def Source.length(s: Source) -> Source & U32: match s: case Buffer{id,+n,chars,lines}: (Buffer{id,n,chars,lines},n) def count_result(id: U32, n: U32, chars: V.Vec, pair: V.Vec & U32) -> Source & U32: (lines,count) = pair (Buffer{id,n,chars,lines},count) def Source.line_count(s: Source) -> Source & U32: match s: case Buffer{id,n,chars,lines}: count_result(id,n,chars,V.Vec.length(U32,lines)) def chars_text(xs: List) -> String: match xs: case Nil{}: SNil{} case Con{h,t}: SCon{h,chars_text(t)} def text_result(id: U32, n: U32, lines: V.Vec, pair: V.Vec & List) -> Source & String: (chars,xs) = pair (Buffer{id,n,chars,lines},chars_text(xs)) def Source.text(s: Source) -> Source & String: match s: case Buffer{id,n,chars,lines}: text_result(id,n,lines,V.Vec.to_list(Char,chars)) def char_result(r: Result) -> Result: match r: case Fail{e}: Fail{T.Bounds{}} case Done{c}: Done{c} def get_result(id: U32, n: U32, lines: V.Vec, pair: V.Vec & Result) -> Source & Result: (chars,r) = pair (Buffer{id,n,chars,lines},char_result(r)) def Source.get(s: Source, i: U32) -> Source & Result: match s: case Buffer{id,n,chars,lines}: get_result(id,n,lines,V.Vec.get(Char,chars,i)) def cursor_if(s: Source, c: T.Cursor, valid: Bool) -> Source & Result: match valid: case False{}: (s,Fail{T.Bounds{}}) case True{}: (s,Done{c}) def Source.cursor(s: Source, +i: U32) -> Source & Result: match s: case Buffer{+id,+n,chars,lines}: cursor_if(Buffer{id,n,chars,lines},T.Cursor{id,i},U32.is_le(i,n)) def Source.checkpoint(c: T.Cursor) -> T.Cursor: c def restore_if(s: Source, i: U32, same: Bool) -> Source & Result: match same: case False{}: (s,Fail{T.ForeignFile{}}) case True{}: Source.cursor(s,i) def Source.restore(s: Source, c: T.Cursor) -> Source & Result: match s c: case Buffer{+own,n,chars,lines} T.Cursor{id,i}: restore_if(Buffer{own,n,chars,lines},i,U32.is_eq(id,own)) def maybe_char(r: Result) -> Result>: match r: case Fail{e}: Fail{e} case Done{c}: Done{Some{c}} def peek_read(pair: Source & Result) -> Source & Result>: (s,r) = pair (s,maybe_char(r)) def peek_at(s: Source, i: U32, eof: Bool) -> Source & Result>: match eof: case True{}: (s,Done{None{}}) case False{}: peek_read(Source.get(s,i)) def peek_checked(s: Source, r: Result) -> Source & Result>: match s r: case Buffer{id,n,chars,lines} Fail{e}: (Buffer{id,n,chars,lines},Fail{e}) case Buffer{id,+n,chars,lines} Done{T.Cursor{other,+i}}: peek_at(Buffer{id,n,chars,lines},i,U32.is_eq(i,n)) def peek_restored(pair: Source & Result) -> Source & Result>: (s,r) = pair peek_checked(s,r) def Source.peek(s: Source, c: T.Cursor) -> Source & Result>: peek_restored(Source.restore(s,c)) def bumped(c: T.Cursor, r: Result>) -> Result>: match c r: case T.Cursor{id,i} Fail{e}: Fail{e} case T.Cursor{id,i} Done{None{}}: Done{(T.Cursor{id,i},None{})} case T.Cursor{id,i} Done{Some{ch}}: Done{(T.Cursor{id,(i + 1 : U32)},Some{ch})} def bump_result(c: T.Cursor, pair: Source & Result>) -> Source & Result>: (s,r) = pair (s,bumped(c,r)) def Source.bump(s: Source, +c: T.Cursor) -> Source & Result>: bump_result(c,Source.peek(s,c)) def span_if(s: Source, span: T.Span, valid: Bool) -> Source & Result: match valid: case False{}: (s,Fail{T.InvalidRange{}}) case True{}: (s,Done{span}) def Source.span(s: Source, +start: U32, +end: U32) -> Source & Result: match s: case Buffer{+id,+n,chars,lines}: span_if(Buffer{id,n,chars,lines},T.Span{id,start,end},Bool.and(U32.is_le(start,end),U32.is_le(end,n))) def slice_result(r: Result>) -> Result: match r: case Fail{e}: Fail{T.InvalidRange{}} case Done{xs}: Done{chars_text(xs)} def extract_result(id: U32, n: U32, lines: V.Vec, pair: V.Vec & Result>) -> Source & Result: (chars,r) = pair (Buffer{id,n,chars,lines},slice_result(r)) def extract_if(s: Source, a: U32, b: U32, same: Bool) -> Source & Result: match s same: case Buffer{id,n,chars,lines} False{}: (Buffer{id,n,chars,lines},Fail{T.ForeignFile{}}) case Buffer{id,n,chars,lines} True{}: extract_result(id,n,lines,V.Vec.slice(Char,chars,a,b)) def Source.extract(s: Source, span: T.Span) -> Source & Result: match s span: case Buffer{+own,n,chars,lines} T.Span{id,a,b}: extract_if(Buffer{own,n,chars,lines},a,b,U32.is_eq(id,own)) # Upper-bound search: greatest start <= offset. 25 fuel covers <= 2^24 lines. type Search is Data: Searching{lo: U32, hi: U32} Found{line: U32} BadIndex{} def split_search(lo: U32, hi: U32, mid: U32, before: Bool) -> Search: match before: case True{}: Searching{lo,mid} case False{}: Searching{mid,hi} def split_read(lo: U32, hi: U32, mid: U32, pos: U32, r: Result) -> Search: match r: case Fail{e}: BadIndex{} case Done{start}: split_search(lo,hi,mid,U32.is_lt(pos,start)) def search_read(lo: U32, hi: U32, mid: U32, pos: U32, pair: V.Vec & Result) -> V.Vec & Search: (lines,r) = pair (lines,split_read(lo,hi,mid,pos,r)) def search_step(lines: V.Vec, +lo: U32, +hi: U32, pos: U32, close: Bool) -> V.Vec & Search: match close: case True{}: (lines,Found{lo}) case False{}: +mid = (lo + ((hi - lo : U32) / 2 : U32) : U32) search_read(lo,hi,mid,pos,V.Vec.get(U32,lines,mid)) def search(fuel: Nat, +pos: U32, pair: V.Vec & Search) -> V.Vec & Search: match fuel pair: case 0n Tuple{lines,Searching{lo,hi}}: (lines,BadIndex{}) case 0n Tuple{lines,Found{line}}: (lines,Found{line}) case 0n Tuple{lines,BadIndex{}}: (lines,BadIndex{}) case 1n+f Tuple{lines,Found{line}}: (lines,Found{line}) case 1n+f Tuple{lines,BadIndex{}}: (lines,BadIndex{}) case 1n+f Tuple{lines,Searching{+lo,+hi}}: search(f,pos,search_step(lines,lo,hi,pos,U32.is_le((hi - lo : U32),1))) def location_read(line: U32, pos: U32, r: Result) -> Result: match r: case Fail{e}: Fail{T.IndexInvariant{}} case Done{start}: Done{T.Location{line,(pos - start : U32)}} def locate_read(id: U32, n: U32, chars: V.Vec, line: U32, pos: U32, pair: V.Vec & Result) -> Source & Result: (lines,r) = pair (Buffer{id,n,chars,lines},location_read(line,pos,r)) def location_finish(id: U32, n: U32, chars: V.Vec, lines: V.Vec, pos: U32, state: Search) -> Source & Result: match state: case BadIndex{}: (Buffer{id,n,chars,lines},Fail{T.IndexInvariant{}}) case Searching{lo,hi}: (Buffer{id,n,chars,lines},Fail{T.IndexInvariant{}}) case Found{+line}: locate_read(id,n,chars,line,pos,V.Vec.get(U32,lines,line)) def locate_searched(id: U32, n: U32, chars: V.Vec, pos: U32, pair: V.Vec & Search) -> Source & Result: (lines,state) = pair location_finish(id,n,chars,lines,pos,state) def locate_counted(id: U32, n: U32, chars: V.Vec, +pos: U32, pair: V.Vec & U32) -> Source & Result: (lines,count) = pair locate_searched(id,n,chars,pos,search(25n,pos,(lines,Searching{0,count}))) def locate_if(s: Source, +pos: U32, valid: Bool) -> Source & Result: match s valid: case Buffer{id,n,chars,lines} False{}: (Buffer{id,n,chars,lines},Fail{T.Bounds{}}) case Buffer{id,n,chars,lines} True{}: locate_counted(id,n,chars,pos,V.Vec.length(U32,lines)) def Source.locate(s: Source, +pos: U32) -> Source & Result: match s: case Buffer{id,+n,chars,lines}: locate_if(Buffer{id,n,chars,lines},pos,U32.is_le(pos,n)) def offset_valid(start: U32, col: U32, valid: Bool) -> Result: match valid: case False{}: Fail{T.Bounds{}} case True{}: Done{(start + col : U32)} def offset_bound(+start: U32, +col: U32, next: Result) -> Result: match next: case Fail{e}: Fail{T.IndexInvariant{}} case Done{end}: offset_valid(start,col,U32.is_lt(col,(end - start : U32))) def offset_next(start: U32, col: U32, pair: V.Vec & Result) -> V.Vec & Result: (lines,b) = pair (lines,offset_bound(start,col,b)) def offset_line(lines: V.Vec, +line: U32, +col: U32, n: U32, start: Result, last: Bool) -> V.Vec & Result: match start last: case Fail{e} False{}: (lines,Fail{T.Bounds{}}) case Fail{e} True{}: (lines,Fail{T.Bounds{}}) case Done{+a} True{}: (lines,offset_valid(a,col,U32.is_le(col,(n - a : U32)))) case Done{a} False{}: offset_next(a,col,V.Vec.get(U32,lines,(line + 1 : U32))) def offset_finish(id: U32, n: U32, chars: V.Vec, pair: V.Vec & Result) -> Source & Result: (lines,r) = pair (Buffer{id,n,chars,lines},r) def offset_read(id: U32, +n: U32, chars: V.Vec, line: U32, col: U32, last: Bool, pair: V.Vec & Result) -> Source & Result: (lines,a) = pair offset_finish(id,n,chars,offset_line(lines,line,col,n,a,last)) def offset_counted(id: U32, n: U32, chars: V.Vec, +line: U32, col: U32, pair: V.Vec & U32) -> Source & Result: (lines,count) = pair offset_read(id,n,chars,line,col,U32.is_eq(line,(count - 1 : U32)),V.Vec.get(U32,lines,line)) def Source.offset(s: Source, loc: T.Location) -> Source & Result: match s loc: case Buffer{id,n,chars,lines} T.Location{line,col}: offset_counted(id,n,chars,line,col,V.Vec.length(U32,lines))