1 # rule hoist: a fixed table built inside a def that calls itself, from inputs 2 # that do not change, and then indexed. The build runs again on every step. 3 # Build it once, outside the recursion. A build is a list or array literal, 4 # `List.replicate` / `List.range` / `List.map` / `Array.new` / `Array.map`, or 5 # a call to a def of this file whose body is itself one fixed table of more 6 # than eight cells; a def whose body cannot be seen (another module's) is not 7 # a build. A table of eight cells or fewer is left alone, and so is a build 8 # whose arguments depend on the step. A name that is not a carried parameter 9 # (a pattern binder, a value computed on the step) keeps the build; a case arm 10 # that does not recurse is cold; laws and proofs do not run (a def with no 11 # type at all fills a law: it is a proof). `List.get` itself stays with 12 # `index`. 13 import Base 14 import ../../src.bend as Src 15 import ../../finding.bend as F 16 import ../../syntax/lex.bend as Lex 17 import ../../syntax/tree.bend as Tree 18 import ../calls.bend as Calls 19 import ../../lazy/lazy.bend as Lazy 20 import ./table.bend as Table 21 22 # where an accessor reads its collection, when it is one 23 type Slot is Data: 24 Slot{on: Bool, at: Nat} 25 26 # a `name = expr` whose left side is one plain name 27 type Got is Data: 28 Got{nm: String, expr: Tree.Node} 29 30 # which argument a get or set indexes 31 def slot_pick(+list: Bool, +str: Bool, +arr: Bool) -> Slot: 32 match list str arr: 33 case True{} b c: 34 Slot{True{}, 2n} 35 case False{} True{} c: 36 Slot{True{}, 0n} 37 case False{} False{} True{}: 38 Slot{True{}, 1n} 39 case False{} False{} False{}: 40 Slot{False{}, 0n} 41 42 # List.get / List.set read argument 2, String.get argument 0, Array.get / Array.set argument 1 43 def slot_of(+tt: String) -> Slot: 44 slot_pick( 45 Bool.or(String.eq(tt, "List.get"), String.eq(tt, "List.set")), 46 String.eq(tt, "String.get"), 47 Bool.or(String.eq(tt, "Array.get"), String.eq(tt, "Array.set"))) 48 49 # the self-calls' argument lists 50 def pushes(nn: Tree.Node, +self: String) -> List<&2, List<&2, Tree.Node>>: 51 match nn: 52 case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: 53 +me = Bool.and(String.eq(o, "("), String.eq(t, self)) 54 +more = List.concat(&2, List<&2, Tree.Node>, [pushes(kids, self), pushes(rest, self)]) 55 Bool.pick(List<&2, List<&2, Tree.Node>>, me, Calls.args(kids) <> more, more) 56 case Tree.NCons{Tree.Group{open, +kids, close}, rest}: 57 List.concat(&2, List<&2, Tree.Node>, [pushes(kids, self), pushes(rest, self)]) 58 case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: 59 List.concat(&2, List<&2, Tree.Node>, [pushes(kids, self), pushes(body, self), pushes(rest, self)]) 60 case Tree.NCons{h, rest}: 61 pushes(rest, self) 62 case other: 63 Nil{} 64 65 # is the chain exactly this name? 66 def lone_eq(nn: Tree.Node, +name: String) -> Bool: 67 match nn: 68 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}: 69 String.eq(t, name) 70 case other: 71 False{} 72 73 # every self-call passes this same name at this argument 74 def all_same(cs: List<&2, List<&2, Tree.Node>>, +ii: Nat, +name: String) -> Bool: 75 match cs: 76 case Nil{}: 77 True{} 78 case Con{as, rest}: 79 +here = lone_eq(Calls.arg(as, ii), name) 80 +more = all_same(rest, ii, name) 81 Bool.and(here, more) 82 83 # parameters every self-call passes unchanged, when there is a self-call 84 def carried.go( 85 ps: List<&2, Tree.Node>, 86 +cs: List<&2, List<&2, Tree.Node>>, 87 +ii: Nat, 88 +acc: List<&2, String> 89 ) -> List<&2, String>: 90 match ps: 91 case Nil{}: 92 List.reverse(&2, String, acc) 93 case Con{p, rest}: 94 +nm = Calls.param_name(p) 95 +keep = Bool.and(Bool.not(String.is_empty(nm)), all_same(cs, ii, nm)) 96 carried.go(rest, cs, (ii + 1n : Nat), Bool.pick(List<&2, String>, keep, nm <> acc, acc)) 97 98 # parameters passed through unchanged; empty when nothing recurses 99 def carried_of(cs: List<&2, List<&2, Tree.Node>>, +sig: Tree.Node) -> List<&2, String>: 100 match cs: 101 case Nil{}: 102 Nil{} 103 case Con{h, t}: 104 carried.go(Calls.params(sig), h <> t, 0n, []) 105 106 # parameters passed through unchanged; empty when nothing recurses 107 def carried(sig: Tree.Node, body: Tree.Node, +self: String) -> List<&2, String>: 108 carried_of(pushes(body, self), sig) 109 110 # a plain name is closed only when it is a carried parameter; a type or a dotted name is 111 def name_ok(kk: Lex.TokKind, +tt: String, +carried: List<&2, String>) -> Bool: 112 match kk: 113 case Lex.TName{}: 114 List.contains(~String, ~String.eq, carried, tt) 115 case other: 116 True{} 117 118 # a token after an operator: a template argument is not a value, anything else is 119 def leaf_ok(+skip: Bool, tok: Lex.Tok, +carried: List<&2, String>) -> Bool: 120 match skip: 121 case True{}: 122 True{} 123 case False{}: 124 Lex.Tok{+k, +t, l, c} = tok 125 name_ok(k, t, carried) 126 127 # an application is closed in its arguments; a bracketed name is a value 128 def value_ok(+app: Bool, kk: Lex.TokKind, +tt: String, +carried: List<&2, String>) -> Bool: 129 match app: 130 case True{}: 131 True{} 132 case False{}: 133 name_ok(kk, tt, carried) 134 135 # every value name under the node is a carried parameter (callees and templates are not values) 136 def closed(nn: Tree.Node, +carried: List<&2, String>) -> Bool: 137 match nn: 138 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, +op, l, c}}, Tree.NCons{Tree.Leaf{+tok}, tail}}: 139 +ok = leaf_ok(String.eq(op, "~"), tok, carried) 140 +aft = closed(tail, carried) 141 Bool.and(ok, aft) 142 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, op, l, c}}, rest}: 143 closed(rest, carried) 144 case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: 145 +ok = value_ok(String.eq(o, "("), k, t, carried) 146 +inn = closed(kids, carried) 147 +aft = closed(rest, carried) 148 Bool.and(ok, Bool.and(inn, aft)) 149 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, rest}: 150 +ok = List.contains(~String, ~String.eq, carried, t) 151 +aft = closed(rest, carried) 152 Bool.and(ok, aft) 153 case Tree.NCons{Tree.Leaf{tok}, rest}: 154 closed(rest, carried) 155 case Tree.NCons{Tree.Group{open, +kids, close}, rest}: 156 +inn = closed(kids, carried) 157 +aft = closed(rest, carried) 158 Bool.and(inn, aft) 159 case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: 160 +inn = closed(kids, carried) 161 +bod = closed(body, carried) 162 +aft = closed(rest, carried) 163 Bool.and(inn, Bool.and(bod, aft)) 164 case Tree.NCons{h, rest}: 165 closed(rest, carried) 166 case Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}: 167 List.contains(~String, ~String.eq, carried, t) 168 case other: 169 True{} 170 171 # a top-level token of this text, not one nested in a group 172 def top_mark(nn: Tree.Node, +want: String) -> Bool: 173 match nn: 174 case Tree.NCons{Tree.Group{open, kids, close}, rest}: 175 top_mark(rest, want) 176 case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, rest}: 177 +more = top_mark(rest, want) 178 Bool.or(String.eq(t, want), more) 179 case Tree.NCons{h, rest}: 180 top_mark(rest, want) 181 case other: 182 False{} 183 184 # `[v : T*n]` or `[v : T^d]`: an array, not a list 185 def is_array(+kids: Tree.Node) -> Bool: 186 +col = top_mark(kids, ":") 187 +star = top_mark(kids, "*") 188 +hat = top_mark(kids, "^") 189 Bool.and(col, Bool.or(star, hat)) 190 191 # how many top-level copies of this token 192 def top_count(nn: Tree.Node, +want: String, +got: Nat) -> Nat: 193 match nn: 194 case Tree.NCons{Tree.Group{open, kids, close}, rest}: 195 top_count(rest, want, got) 196 case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, rest}: 197 +next = Bool.pick(Nat, String.eq(t, want), (got + 1n : Nat), got) 198 top_count(rest, want, next) 199 case Tree.NCons{h, rest}: 200 top_count(rest, want, got) 201 case other: 202 got 203 204 # a number literal of eight or fewer 205 def small_num(+tt: String) -> Bool: 206 List.contains(~String, ~String.eq, 207 ["0", "1", "2", "3", "4", "5", "6", "7", "8", "0n", "1n", "2n", "3n", "4n", "5n", "6n", "7n", "8n"], tt) 208 209 # the size after `*` is a literal of eight or fewer, or the depth after `^` (2^d slots) three or fewer 210 def small_size(nn: Tree.Node) -> Bool: 211 match nn: 212 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TOp{}, +op, l, c}}, 213 Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, +t, l2, c2}}, rest}}: 214 +here = Bool.or(Bool.and(String.eq(op, "*"), small_num(t)), 215 Bool.and(String.eq(op, "^"), List.contains(~String, ~String.eq, ["0", "1", "2", "3", "0n", "1n", "2n", "3n"], t))) 216 +more = small_size(rest) 217 Bool.or(here, more) 218 case Tree.NCons{Tree.Group{open, kids, close}, rest}: 219 small_size(rest) 220 case Tree.NCons{h, rest}: 221 small_size(rest) 222 case other: 223 False{} 224 225 # more than eight cells: nine or more list elements, or an array whose size is not that small 226 def wide_shape(+arr: Bool, +kids: Tree.Node) -> Bool: 227 match arr: 228 case True{}: 229 Bool.not(small_size(kids)) 230 case False{}: 231 Nat.is_gt(top_count(kids, ",", 0n), 7n) 232 233 # constructors that build a fixed table rather than walk one 234 def allowed(+tt: String) -> Bool: 235 List.contains(~String, ~String.eq, 236 ["List.replicate", "List.range", "List.map", "Array.new", "Array.map"], tt) 237 238 # a Base constructor on the allow list, or a def of this file whose body is 239 # itself a wide table (`wides`); a def whose body cannot be seen is not one 240 def builder(kk: Lex.TokKind, +tt: String, +self: String, +wides: List<&2, String>) -> Bool: 241 match kk: 242 case Lex.TName{}: 243 Bool.and(Bool.not(String.eq(tt, self)), List.contains(~String, ~String.eq, wides, tt)) 244 case Lex.TDotted{}: 245 Bool.or(allowed(tt), List.contains(~String, ~String.eq, wides, tt)) 246 case other: 247 False{} 248 249 # a bracket group that is a closed list or array of more than eight cells 250 def group_yes(+arr: Bool, +kids: Tree.Node, +carried: List<&2, String>) -> Bool: 251 +shape = Bool.or(top_mark(kids, ","), arr) 252 +ok = closed(kids, carried) 253 +wide = wide_shape(arr, kids) 254 Bool.and(shape, Bool.and(ok, wide)) 255 256 # a bracket group that is a closed list or array 257 def table_group(+brack: Bool, +kids: Tree.Node, +carried: List<&2, String>) -> Bool: 258 match brack: 259 case False{}: 260 False{} 261 case True{}: 262 group_yes(is_array(kids), kids, carried) 263 264 # the chain is exactly one number; its text, or empty 265 def num_text(nn: Tree.Node) -> String: 266 match nn: 267 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TNum{}, +t, l, c}}, Tree.NNil{}}: 268 t 269 case other: 270 "" 271 272 # a count is wide when it is not a literal of eight or fewer (a name may be large) 273 def count_wide(aa: Tree.Node) -> Bool: 274 Bool.not(small_num(num_text(aa))) 275 276 # the last argument 277 def last_node(as: List<&2, Tree.Node>) -> Tree.Node: 278 match as: 279 case Nil{}: 280 Tree.NNil{} 281 case Con{h, Nil{}}: 282 h 283 case Con{h, rest}: 284 last_node(rest) 285 286 # `List.replicate` / `Array.new` take argument 2; anything else is wide unless it is `List.range` 287 def call_arm(rep: Bool, +as: List<&2, Tree.Node>, +end: Tree.Node, +range: Bool) -> Bool: 288 match rep: 289 case True{}: 290 count_wide(Calls.arg(as, 2n)) 291 case False{}: 292 Bool.pick(Bool, range, count_wide(end), True{}) 293 294 # `List.replicate` / `Array.new` / `List.range` are wide unless their count is a small literal 295 def call_wide(+tt: String, kids: Tree.Node) -> Bool: 296 +as = Calls.args(kids) 297 call_arm(Bool.or(String.eq(tt, "List.replicate"), String.eq(tt, "Array.new")), as, last_node(as), String.eq(tt, "List.range")) 298 299 # a call that builds a table from closed arguments, and is not a small literal count 300 def table_built(+ok: Bool, +wide: Bool, kids: Tree.Node, +carried: List<&2, String>) -> Bool: 301 match ok wide: 302 case True{} True{}: 303 closed(kids, carried) 304 case True{} False{}: 305 False{} 306 case False{} b: 307 False{} 308 309 # a call that builds a table from closed arguments 310 def table_call( 311 +app: Bool, 312 kk: Lex.TokKind, 313 +tt: String, 314 +kids: Tree.Node, 315 +self: String, 316 +wides: List<&2, String>, 317 +carried: List<&2, String> 318 ) -> Bool: 319 match app: 320 case False{}: 321 False{} 322 case True{}: 323 table_built(builder(kk, tt, self, wides), call_wide(tt, kids), kids, carried) 324 325 # a fixed table of more than eight cells, whatever its cells read 326 def wide_of(nn: Tree.Node) -> Bool: 327 match nn: 328 case Tree.NCons{Tree.Group{Lex.Tok{k, +o, l, c}, +kids, close}, Tree.NNil{}}: 329 wide_shape(is_array(kids), kids) 330 case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, 331 Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, Tree.NNil{}}}: 332 call_wide(t, kids) 333 case other: 334 False{} 335 336 # a def whose body is one wide fixed table (`Table.built`, and more than eight cells) 337 def wide_one(body: Tree.Node, +name: String, +acc: List<&2, String>) -> List<&2, String>: 338 match body: 339 case Tree.NCons{Tree.Stmt{kind, +kids, inn}, Tree.NNil{}}: 340 Bool.pick(List<&2, String>, Bool.and(Table.built(kids), wide_of(kids)), name <> acc, acc) 341 case other: 342 acc 343 344 # defs of this file whose body is one wide fixed table 345 def wide_defs(ds: List<&2, Calls.Def>, acc: List<&2, String>) -> List<&2, String>: 346 match ds: 347 case Nil{}: 348 acc 349 case Con{Calls.Def{+name, sig, +body}, rest}: 350 wide_defs(rest, wide_one(body, name, acc)) 351 352 # a list, an array, or a builder call, closed over carried parameters 353 def is_table(nn: Tree.Node, +self: String, +wides: List<&2, String>, +carried: List<&2, String>) -> Bool: 354 match nn: 355 case Tree.NCons{Tree.Group{Lex.Tok{k, +o, l, c}, +kids, close}, Tree.NNil{}}: 356 table_group(String.eq(o, "["), kids, carried) 357 case Tree.NCons{Tree.Leaf{Lex.Tok{+k, +t, l, c}}, 358 Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, Tree.NNil{}}}: 359 table_call(String.eq(o, "("), k, t, kids, self, wides, carried) 360 case other: 361 False{} 362 363 # drop the right-hand side 364 def skip_expr(nn: Tree.Node) -> Maybe<&2, Got>: 365 match nn: 366 case other: 367 None{} 368 369 # the chain after `=`, when the left side is one plain name and nothing else 370 def got_bad(+bad: Bool, +nm: String, expr: Tree.Node) -> Maybe<&2, Got>: 371 match bad: 372 case True{}: 373 skip_expr(expr) 374 case False{}: 375 Some{Got{nm, expr}} 376 377 # the chain after `=`, when exactly one plain name stands before it 378 def got_at(+seen: Bool, +bad: Bool, +nm: String, expr: Tree.Node) -> Maybe<&2, Got>: 379 match seen: 380 case False{}: 381 skip_expr(expr) 382 case True{}: 383 got_bad(bad, nm, expr) 384 385 # a second name, a constructor or a group on the left is not a plain binder 386 def eat(nn: Tree.Node, +seen: Bool, +bad: Bool, +nm: String) -> Maybe<&2, Got>: 387 match nn: 388 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TEq{}, t, l, c}}, rest}: 389 got_at(seen, bad, nm, rest) 390 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, rest}: 391 eat(rest, True{}, Bool.or(bad, seen), t) 392 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TUpper{}, t, l, c}}, rest}: 393 eat(rest, seen, True{}, nm) 394 case Tree.NCons{Tree.Group{open, kids, close}, rest}: 395 eat(rest, seen, True{}, nm) 396 case Tree.NCons{h, rest}: 397 eat(rest, seen, bad, nm) 398 case other: 399 None{} 400 401 # a finding on the table expression 402 def cite(+nn: Tree.Node, +path: String) -> List<&2, F.Finding>: 403 [F.Finding{path, Tree.line(nn), Tree.col(nn), U32.from_nat(String.length(Tree.text(nn))), "hoist", 404 "This table is rebuilt on every step from inputs that do not change; build it once, outside the recursion."}] 405 406 # a name, and nothing after it 407 def wrapped_inside(nn: Tree.Node, +binder: String) -> Bool: 408 match nn: 409 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}: 410 String.eq(t, binder) 411 case other: 412 False{} 413 414 # inside one pair of parentheses 415 def wrapped_open(+paren: Bool, kids: Tree.Node, +binder: String) -> Bool: 416 match paren: 417 case True{}: 418 wrapped_inside(kids, binder) 419 case False{}: 420 False{} 421 422 # the chain is exactly this name, or that name in parentheses 423 def wrapped(nn: Tree.Node, +binder: String) -> Bool: 424 match nn: 425 case Tree.NCons{Tree.Leaf{Lex.Tok{Lex.TName{}, +t, l, c}}, Tree.NNil{}}: 426 String.eq(t, binder) 427 case Tree.NCons{Tree.Group{Lex.Tok{k, +o, l, c}, +kids, close}, Tree.NNil{}}: 428 wrapped_open(String.eq(o, "("), kids, binder) 429 case other: 430 False{} 431 432 # the collection argument is this name 433 def uses_arg(ss: Slot, +as: List<&2, Tree.Node>, +binder: String) -> Bool: 434 Slot{on, at} = ss 435 match on: 436 case False{}: 437 False{} 438 case True{}: 439 wrapped(Calls.arg(as, at), binder) 440 441 # `name[` is an index; a call is an index when its collection argument is the name 442 def uses_here_call(+call: Bool, +tt: String, kids: Tree.Node, +binder: String) -> Bool: 443 match call: 444 case False{}: 445 False{} 446 case True{}: 447 uses_arg(slot_of(tt), Calls.args(kids), binder) 448 449 # `name[` is an index; a call is an index when its collection argument is the name 450 def uses_here(+brack: Bool, +call: Bool, +tt: String, kids: Tree.Node, +binder: String) -> Bool: 451 match brack: 452 case True{}: 453 String.eq(tt, binder) 454 case False{}: 455 uses_here_call(call, tt, kids, binder) 456 457 # the name is indexed later: `name[`, or the collection of a get or set 458 def uses_name(nn: Tree.Node, +binder: String) -> Bool: 459 match nn: 460 case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: 461 +here = uses_here(String.eq(o, "["), String.eq(o, "("), t, kids, binder) 462 +inn = uses_name(kids, binder) 463 +aft = uses_name(rest, binder) 464 Bool.or(here, Bool.or(inn, aft)) 465 case Tree.NCons{Tree.Group{open, +kids, close}, rest}: 466 +inn = uses_name(kids, binder) 467 +aft = uses_name(rest, binder) 468 Bool.or(inn, aft) 469 case Tree.NCons{Tree.Stmt{kind, +kids, body}, rest}: 470 +inn = uses_name(kids, binder) 471 +bod = uses_name(body, binder) 472 +aft = uses_name(rest, binder) 473 Bool.or(inn, Bool.or(bod, aft)) 474 case Tree.NCons{h, rest}: 475 uses_name(rest, binder) 476 case other: 477 False{} 478 479 # indexed in the let's body or in a following statement 480 def uses_or(body: Tree.Node, rest: Tree.Node, +binder: String) -> Bool: 481 +inn = uses_name(body, binder) 482 +aft = uses_name(rest, binder) 483 Bool.or(inn, aft) 484 485 # a finding when the table is also indexed 486 def let_used(+used: Bool, +expr: Tree.Node, +path: String) -> List<&2, F.Finding>: 487 match used: 488 case True{}: 489 cite(expr, path) 490 case False{}: 491 Nil{} 492 493 # a finding when the right-hand side is a closed table that is indexed 494 def let_use(+tab: Bool, +used: Bool, +expr: Tree.Node, +path: String) -> List<&2, F.Finding>: 495 match tab: 496 case False{}: 497 Nil{} 498 case True{}: 499 let_used(used, expr, path) 500 501 # a plain let of a table 502 def let_of( 503 mm: Maybe<&2, Got>, 504 +body: Tree.Node, 505 +rest: Tree.Node, 506 +self: String, 507 +wides: List<&2, String>, 508 +carried: List<&2, String>, 509 +path: String 510 ) -> List<&2, F.Finding>: 511 match mm: 512 case None{}: 513 Nil{} 514 case Some{Got{+nm, +expr}}: 515 let_use(is_table(expr, self, wides, carried), uses_or(body, rest, nm), expr, path) 516 517 # a hot plain let of a table that is indexed afterwards 518 def let_hit( 519 +hot: Bool, 520 kids: Tree.Node, 521 +body: Tree.Node, 522 +rest: Tree.Node, 523 +self: String, 524 +wides: List<&2, String>, 525 +carried: List<&2, String>, 526 +path: String 527 ) -> List<&2, F.Finding>: 528 match hot: 529 case False{}: 530 Nil{} 531 case True{}: 532 let_of(eat(kids, False{}, False{}, ""), body, rest, self, wides, carried, path) 533 534 # a finding when this argument is itself a closed table 535 def one_yes(+yes: Bool, +aa: Tree.Node, +path: String) -> List<&2, F.Finding>: 536 match yes: 537 case True{}: 538 cite(aa, path) 539 case False{}: 540 Nil{} 541 542 # the collection argument, when this call indexes one 543 def arg_table( 544 ss: Slot, 545 +as: List<&2, Tree.Node>, 546 +self: String, 547 +wides: List<&2, String>, 548 +carried: List<&2, String>, 549 +path: String 550 ) -> List<&2, F.Finding>: 551 Slot{on, at} = ss 552 match on: 553 case False{}: 554 Nil{} 555 case True{}: 556 +aa = Calls.arg(as, at) 557 one_yes(is_table(aa, self, wides, carried), aa, path) 558 559 # an inline table passed straight to a get or set, when the group is a call 560 def call_open( 561 +open: Bool, 562 +tt: String, 563 kids: Tree.Node, 564 +self: String, 565 +wides: List<&2, String>, 566 +carried: List<&2, String>, 567 +path: String 568 ) -> List<&2, F.Finding>: 569 match open: 570 case False{}: 571 Nil{} 572 case True{}: 573 arg_table(slot_of(tt), Calls.args(kids), self, wides, carried, path) 574 575 # an inline table passed straight to a get or set 576 def call_table( 577 +hot: Bool, 578 +open: Bool, 579 +tt: String, 580 kids: Tree.Node, 581 +self: String, 582 +wides: List<&2, String>, 583 +carried: List<&2, String>, 584 +path: String 585 ) -> List<&2, F.Finding>: 586 match hot: 587 case False{}: 588 Nil{} 589 case True{}: 590 call_open(open, tt, kids, self, wides, carried, path) 591 592 # a `=>` cools the rest of its chain: a lambda's body runs when it is called, not per step 593 def warm(+hot: Bool, kk: Lex.TokKind) -> Bool: 594 match kk: 595 case Lex.TLam{}: 596 False{} 597 case other: 598 hot 599 600 # tables built in a hot region 601 def walk( 602 nn: Tree.Node, 603 +self: String, 604 +wides: List<&2, String>, 605 +carried: List<&2, String>, 606 +path: String, 607 +hot: Bool 608 ) -> List<&2, F.Finding>: 609 match nn: 610 case Tree.NCons{Tree.Leaf{Lex.Tok{k, +t, l, c}}, Tree.NCons{Tree.Group{Lex.Tok{_, +o, _, _}, +kids, _}, rest}}: 611 +heat = warm(hot, k) 612 +own = call_table(heat, String.eq(o, "("), t, kids, self, wides, carried, path) 613 +inn = walk(kids, self, wides, carried, path, heat) 614 +aft = walk(rest, self, wides, carried, path, heat) 615 List.concat(&2, F.Finding, [own, inn, aft]) 616 case Tree.NCons{Tree.Leaf{Lex.Tok{k, t, l, c}}, rest}: 617 walk(rest, self, wides, carried, path, warm(hot, k)) 618 case Tree.NCons{Tree.Group{open, +kids, close}, rest}: 619 List.concat(&2, F.Finding, 620 [walk(kids, self, wides, carried, path, hot), walk(rest, self, wides, carried, path, hot)]) 621 case Tree.NCons{Tree.Stmt{Tree.SCase{}, kids, +body}, rest}: 622 +arm = Bool.and(hot, Calls.calls(body, self)) 623 List.concat(&2, F.Finding, [walk(kids, self, wides, carried, path, False{}), 624 walk(body, self, wides, carried, path, arm), walk(rest, self, wides, carried, path, hot)]) 625 case Tree.NCons{Tree.Stmt{kind, +kids, +body}, +rest}: 626 +own = let_hit(hot, kids, body, rest, self, wides, carried, path) 627 List.concat(&2, F.Finding, [own, walk(kids, self, wides, carried, path, hot), 628 walk(body, self, wides, carried, path, hot), walk(rest, self, wides, carried, path, hot)]) 629 case Tree.NCons{h, rest}: 630 walk(rest, self, wides, carried, path, hot) 631 case other: 632 Nil{} 633 634 def check.go( 635 ds: List<&2, Calls.Def>, 636 +wide: List<&2, String>, 637 +path: String, 638 acc: List<&2, List<&2, F.Finding>> 639 ) -> List<&2, F.Finding>: 640 match ds: 641 case Nil{}: 642 List.concat(&2, F.Finding, List.reverse(&2, List<&2, F.Finding>, acc)) 643 case Con{Calls.Def{+name, +sig, +body}, rest}: 644 check.go(rest, wide, path, 645 Lazy.stop(List<&2, F.Finding>, 646 Bool.not(Bool.and(Calls.calls(body, name), Bool.not(Calls.exempt(path, sig)))), [], 647 _u => walk(body, name, wide, carried(sig, body, name), path, True{})) <> acc) 648 649 # the rule 650 def check(ss: Src.Src) -> List<&2, F.Finding>: 651 Src.Src{path, text, toks, tree, bound, items} = ss 652 +ds = Calls.defs(tree) 653 check.go(ds, wide_defs(ds, []), path, [])