1 # rule fromrev: a String read off a reversed list: among the significant 2 # tokens (spaces, newlines and comments dropped), four tokens right after one 3 # another whose texts are `String.from_list`, `(`, `List.reverse` and `(`; 4 # one finding, on the `String.from_list` token, for each such run, and 5 # nothing else. Anything between them, another `(` included 6 # (`String.from_list((List.reverse(..)))`), is not the shape, and neither is 7 # `List.reverse.go`. A buffer of chars consed on the front, then reversed 8 # and read into a String, walks the buffer twice and builds a list only to 9 # throw it away (and String.from_list, not a tail call, runs the JS lane out 10 # of stack on a long buffer): fold the buffer onto a String with SCon in one 11 # pass, `go(t, SCon{h, acc})` from `SNil{}` (src/rchars.bend). A LAWS.bend 12 # or a PROOF.bend, which never runs, is exempt. 13 import Base 14 import ../../src.bend as Src 15 import ../../paths.bend as Paths 16 import ../../lazy/lazy.bend as Lazy 17 import ../../finding.bend as F 18 import ../../syntax/lex.bend as Lex 19 import ../tokens.bend as T 20 21 # are the four texts `String.from_list`, `(`, `List.reverse` and `(`? 22 def shape(+tt: String, +aa: String, +bb: String, +dd: String) -> Bool: 23 Bool.and(String.eq(tt, "String.from_list"), 24 Bool.and(String.eq(aa, "("), Bool.and(String.eq(bb, "List.reverse"), String.eq(dd, "(")))) 25 26 # a token of text tt, then these: do the four open the shape? 27 def at(+tt: String, toks: List<&2, Lex.Tok>) -> Bool: 28 match toks: 29 case Con{Lex.Tok{k1, +aa, l1, c1}, Con{Lex.Tok{k2, +bb, l2, c2}, Con{Lex.Tok{k3, +dd, l3, c3}, rest}}}: 30 shape(tt, aa, bb, dd) 31 case other: 32 False{} 33 34 # every run of the shape among the tokens, in order 35 def walk(toks: List<&2, Lex.Tok>, +path: String) -> List<&2, F.Finding>: 36 match toks: 37 case Nil{}: 38 Nil{} 39 case Con{Lex.Tok{k, +t, +l, +c}, +rest}: 40 +more = walk(rest, path) 41 Bool.pick(List<&2, F.Finding>, at(t, rest), 42 F.Finding{path, l, c, 16, "fromrev", "String.from_list(List.reverse(..)) walks the list twice and builds one it throws away; fold it onto a String with SCon in one pass."} 43 <> more, 44 more) 45 46 # the rule 47 def check(ss: Src.Src) -> List<&2, F.Finding>: 48 Src.Src{+path, text, toks, tree, bound, items} = ss 49 Lazy.stop(List<&2, F.Finding>, Paths.is_law_file(path), [], _u => walk(T.sig(toks), path))