1 # src/syntax/outline: a source's top-level items, read line by line. A top-level 2 # item starts at column 0, so a line the outline does not understand costs 3 # nothing: it is skipped, and the items around it still stand. That tolerance 4 # is the point: an editor asks about files that are half-written. 5 # 6 # A line that starts inside a string literal (the lexer's, which runs across 7 # newlines as bend's does) is text, not a line of code: it is never an item, 8 # a comment or a blank, and it joins a pending header as one more line. 9 import Base 10 import ../lazy/lazy.bend as Lazy 11 import ./lex.bend as Lex 12 13 # what an item is; ILocal is not read from a source, it is how the server 14 # offers a binder as a candidate 15 type ItemKind is Data: 16 IImport{} 17 IDef{} 18 ILaw{} 19 IType{} 20 ICtor{} 21 ILocal{} 22 23 # line is 0-based; sig is the header text (a def's through its `:`, a law's 24 # whole block, a constructor's line); doc is the comment block right above; 25 # path is an import's target 26 type Item is Data: 27 Item{kind: ItemKind, name: String, line: U32, sig: String, doc: String, path: String} 28 29 # what a line is, by its first chars 30 type Cls is Data: 31 LBlank{} 32 LComment{text: String} 33 LIndent{text: String} 34 LImport{} 35 LDef{} 36 LLaw{} 37 LType{} 38 LOther{} 39 40 # Pending: a def's header that has not reached its `:`, or a law's block 41 type Mode is Data: 42 Top{} 43 InType{} 44 Pending{kind: ItemKind, name: String, line: U32, doc: String, sig: List<&2, String>} 45 46 # doc and out are reversed 47 type St is Data: 48 St{mode: Mode, n: U32, doc: List<&2, String>, out: List<&2, Item>} 49 50 # the class when the prefix matches, else other 51 def classify.kw(hit: Bool, yes: Cls) -> Cls: 52 match hit: 53 case True{}: 54 yes 55 case False{}: 56 LOther{} 57 58 # leading spaces (reversed) in front of the rest of the line 59 def classify.rebuild(acc: List<&2, Char>, rest: String) -> String: 60 match acc: 61 case Nil{}: 62 rest 63 case Con{h, t}: 64 classify.rebuild(t, SCon{h, rest}) 65 66 # an indented line when the first char is a space, else other 67 def classify.white.hit(indent: Bool, acc: List<&2, Char>, hh: Char, rest: String) -> Cls: 68 match indent: 69 case True{}: 70 LIndent{classify.rebuild(acc, SCon{hh, rest})} 71 case False{}: 72 LOther{} 73 74 # whitespace after a leading space-class char. `space` says `h` is whitespace; 75 # `acc` is the whitespace before `h`, reversed; `indent` when that line opened 76 # with a space 77 def classify.white(tt: String, space: Bool, hh: Char, indent: Bool, acc: List<&2, Char>) -> Cls: 78 match tt space: 79 case SNil{} True{}: 80 LBlank{} 81 case SNil{} False{}: 82 classify.white.hit(indent, acc, hh, SNil{}) 83 case SCon{+h2, r} True{}: 84 classify.white(r, Char.is_space(h2), h2, indent, hh <> acc) 85 case SCon{h2, r} False{}: 86 classify.white.hit(indent, acc, hh, SCon{h2, r}) 87 88 # a line whose first char is whitespace 89 def classify.white.enter(tt: String, +xx: U32) -> Cls: 90 classify.white(tt, True{}, Char.from_u32(xx), U32.is_eq(xx, 32), []) # ' ' 91 92 # `type `, once the `t` is known 93 def classify.ktype(hit: Bool, tt: String) -> Cls: 94 match hit: 95 case True{}: 96 classify.kw(String.starts_with(tt, "ype "), LType{}) 97 case False{}: 98 LOther{} 99 100 # `law `, else `type ` 101 def classify.klaw(hit: Bool, +xx: U32, tt: String) -> Cls: 102 match hit: 103 case True{}: 104 classify.kw(String.starts_with(tt, "aw "), LLaw{}) 105 case False{}: 106 classify.ktype(U32.is_eq(xx, 116), tt) # 't' 107 108 # `@unsafe def `, else `law ` 109 def classify.kat(hit: Bool, +xx: U32, tt: String) -> Cls: 110 match hit: 111 case True{}: 112 classify.kw(String.starts_with(tt, "unsafe def "), LDef{}) 113 case False{}: 114 classify.klaw(U32.is_eq(xx, 108), xx, tt) # 'l' 115 116 # `def `, else `@unsafe def ` 117 def classify.kdef(hit: Bool, +xx: U32, tt: String) -> Cls: 118 match hit: 119 case True{}: 120 classify.kw(String.starts_with(tt, "ef "), LDef{}) 121 case False{}: 122 classify.kat(U32.is_eq(xx, 64), xx, tt) # '@' 123 124 # `import `, else `def ` 125 def classify.kimp(hit: Bool, +xx: U32, tt: String) -> Cls: 126 match hit: 127 case True{}: 128 classify.kw(String.starts_with(tt, "mport "), LImport{}) 129 case False{}: 130 classify.kdef(U32.is_eq(xx, 100), xx, tt) # 'd' 131 132 # a column-0 keyword, from the first character's code 133 def classify.keys(+xx: U32, tt: String) -> Cls: 134 classify.kimp(U32.is_eq(xx, 105), xx, tt) # 'i' 135 136 # a column-0 keyword keeps the line; the class comes from its first character 137 def classify.hold.keys(+xx: U32, cc: Char, +tt: String) -> Cls & String: 138 (classify.keys(xx, tt), SCon{cc, tt}) 139 140 # whitespace that is not a leading space keeps the line: the class may not 141 def classify.hold.gap.keep(+xx: U32, cc: Char, +tt: String) -> Cls & String: 142 (classify.white.enter(tt, xx), SCon{cc, tt}) 143 144 # a leading space is blank or indented, and that class already carries the line 145 def classify.hold.gap(indent: Bool, xx: U32, cc: Char, tt: String) -> Cls & String: 146 match indent: 147 case True{}: 148 (classify.white.enter(tt, xx), SNil{}) 149 case False{}: 150 classify.hold.gap.keep(xx, cc, tt) 151 152 # whitespace, else a keyword 153 def classify.hold.space(space: Bool, +xx: U32, cc: Char, tt: String) -> Cls & String: 154 match space: 155 case True{}: 156 classify.hold.gap(U32.is_eq(xx, 32), xx, cc, tt) # ' ' 157 case False{}: 158 classify.hold.keys(xx, cc, tt) 159 160 # a comment keeps its text; anything else is whitespace or a keyword 161 def classify.hold.hash(is_hash: Bool, space: Bool, xx: U32, cc: Char, tt: String) -> Cls & String: 162 match is_hash: 163 case True{}: 164 (LComment{SCon{cc, tt}}, SNil{}) 165 case False{}: 166 classify.hold.space(space, xx, cc, tt) 167 168 # the first character, then the rest of the line 169 def classify.hold.open(+xx: U32, +cc: Char, tt: String) -> Cls & String: 170 classify.hold.hash(U32.is_eq(xx, 35), Char.is_space(cc), xx, cc, tt) # '#' 171 172 # the class, and the line when the class does not already carry it 173 def classify.hold(ss: String) -> Cls & String: 174 match ss: 175 case SNil{}: 176 (LBlank{}, SNil{}) 177 case SCon{+c, t}: 178 classify.hold.open(Char.to_u32(c), c, t) 179 180 # `(`, `<`, `:`, `{` or a space ends a name 181 def name.stop(+hh: Char) -> Bool: 182 Bool.or(Char.is_eq(hh, '('), 183 Bool.or(Char.is_eq(hh, '<'), 184 Bool.or(Char.is_eq(hh, ':'), 185 Bool.or(Char.is_eq(hh, '{'), Char.is_eq(hh, ' '))))) 186 187 # a delimiter is not part of the name 188 def name.push(stop: Bool, hh: Char, acc: String) -> String: 189 match stop: 190 case True{}: 191 acc 192 case False{}: 193 SCon{hh, acc} 194 195 # the name, reversed into acc, up to a delimiter 196 def name.go(ss: String, acc: String, stop: Bool) -> String: 197 match ss stop: 198 case SNil{} _: 199 String.reverse(acc) 200 case SCon{h, t} True{}: 201 String.reverse(acc) 202 case SCon{+h, t} False{}: 203 name.go(t, name.push(name.stop(h), h, acc), name.stop(h)) 204 205 # the name that starts a header: up to `(`, `<`, `:`, `{` or a space 206 def name(ss: String) -> String: 207 name.go(ss, SNil{}, False{}) 208 209 def lstrip.go(cs: List<&2, Char>) -> List<&2, Char>: 210 match cs: 211 case Con{' ', t}: 212 lstrip.go(t) 213 case other: 214 other 215 216 # leading spaces dropped 217 def lstrip(ss: String) -> String: 218 String.from_list(lstrip.go(String.to_list(ss))) 219 220 # not ""? 221 def nonempty(ss: String) -> Bool: 222 Bool.not(String.is_empty(ss)) 223 224 # the words of a line 225 def words(ss: String) -> List<&2, String>: 226 List.filter(~String, ~nonempty, String.split(ss, ' ')) 227 228 # a comment line without its `# ` 229 def undoc(+ss: String) -> String: 230 Bool.pick(String, String.starts_with(ss, "# "), String.drop(ss, 2n), 231 Bool.pick(String, String.starts_with(ss, "#"), String.drop(ss, 1n), ss)) 232 233 def undoc.all(doc: List<&2, String>, acc: List<&2, String>) -> List<&2, String>: 234 match doc: 235 case Nil{}: 236 acc 237 case Con{h, t}: 238 undoc.all(t, undoc(h) <> acc) 239 240 # the comment block (reversed) as text 241 def doc_text(doc: List<&2, String>) -> String: 242 String.join(undoc.all(doc, []), "\n") 243 244 # the pending header lines (reversed) as one text 245 def sig_text(sig: List<&2, String>) -> String: 246 String.join(List.reverse(&2, String, sig), "\n") 247 248 # an item, when there is one, onto the items 249 def add(mm: Maybe<&2, Item>, out: List<&2, Item>) -> List<&2, Item>: 250 match mm: 251 case None{}: 252 out 253 case Some{item}: 254 item <> out 255 256 # `import as ` is named by its alias; `import Base` by its path 257 def import_item.of(ws: List<&2, String>, ss: String, nn: U32, doc: String) -> Maybe<&2, Item>: 258 match ws: 259 case Con{i, Con{path, Con{as, Con{alias, r}}}}: 260 Some{Item{IImport{}, alias, nn, ss, doc, path}} 261 case Con{i, Con{+path, Nil{}}}: 262 Some{Item{IImport{}, path, nn, ss, doc, path}} 263 case other: 264 None{} 265 266 # an `import` line as an item: `import Base`, or `import as ` 267 def import_item(+ss: String, nn: U32, doc: String) -> Maybe<&2, Item>: 268 import_item.of(words(ss), ss, nn, doc) 269 270 # the name of a `def` or `@unsafe def` line 271 def def_name(+ss: String) -> String: 272 name(Bool.pick(String, String.starts_with(ss, "@"), String.drop(ss, 12n), String.drop(ss, 4n))) 273 274 def starts_upper.of(cs: List<&2, Char>) -> Bool: 275 match cs: 276 case Con{c, t}: 277 Char.is_upper(c) 278 case Nil{}: 279 False{} 280 281 # does the text start with a capital? 282 def starts_upper(ss: String) -> Bool: 283 starts_upper.of(String.to_list(ss)) 284 285 # an indented line of a type block: `Ctor{..}` 286 def ctor.of(+tt: String, nn: U32) -> Maybe<&2, Item>: 287 Lazy.stop(Maybe<&2, Item>, Bool.not(Bool.and(starts_upper(tt), String.contains(tt, "{"))), None{}, 288 _u => Some{Item{ICtor{}, name(tt), nn, tt, "", ""}}) 289 290 # an indented line of a type block as a constructor item, when it is one 291 def ctor(text: String, nn: U32) -> Maybe<&2, Item>: 292 ctor.of(lstrip(text), nn) 293 294 # a def? 295 def is_def(kk: ItemKind) -> Bool: 296 match kk: 297 case IDef{}: 298 True{} 299 case other: 300 False{} 301 302 # what was pending becomes an item 303 def flush(mode: Mode, out: List<&2, Item>) -> List<&2, Item>: 304 match mode: 305 case Pending{k, nm, l, d, sig}: 306 Item{k, nm, l, sig_text(sig), d, ""} <> out 307 case other: 308 out 309 310 # a `:` token? 311 def ends_colon.is(kk: Lex.TokKind) -> Bool: 312 match kk: 313 case Lex.TColon{}: 314 True{} 315 case other: 316 False{} 317 318 # the tokens walked so far ended in `:`; a space or a comment keeps that 319 def ends_colon.go(toks: List<&2, Lex.Tok>, prev: Bool) -> Bool: 320 match toks: 321 case Nil{}: 322 prev 323 case Con{Lex.Tok{+kk, tx, ln, cl}, t}: 324 ends_colon.go(t, Bool.pick(Bool, Lex.significant(kk), ends_colon.is(kk), prev)) 325 326 # the line's last token that is not a space or a comment is `:`, so a header 327 # ends at its `:` though a comment follows it (`def f() -> U32: # note`) 328 def ends_colon(ss: String) -> Bool: 329 ends_colon.go(Lex.tokens(ss), False{}) 330 331 # a def continuation that does or does not end in `:` 332 def pend_more.closed( 333 colon: Bool, 334 nm: String, 335 ll: U32, 336 dd: String, 337 sig: List<&2, String>, 338 text: String, 339 nn: U32, 340 out: List<&2, Item> 341 ) -> St: 342 match colon: 343 case True{}: 344 St{Top{}, (nn + 1 : U32), [], Item{IDef{}, nm, ll, sig_text(text <> sig), dd, ""} <> out} 345 case False{}: 346 St{Pending{IDef{}, nm, ll, dd, text <> sig}, (nn + 1 : U32), [], out} 347 348 # one more line of a def header 349 def pend_more.def( 350 nm: String, 351 ll: U32, 352 dd: String, 353 sig: List<&2, String>, 354 +text: String, 355 nn: U32, 356 out: List<&2, Item> 357 ) -> St: 358 pend_more.closed(ends_colon(text), nm, ll, dd, sig, text, nn, out) 359 360 # one more line of a pending header; a def's ends at the line that ends in `:` 361 def pend_more( 362 kk: ItemKind, 363 nm: String, 364 ll: U32, 365 dd: String, 366 sig: List<&2, String>, 367 text: String, 368 nn: U32, 369 out: List<&2, Item> 370 ) -> St: 371 match kk: 372 case IDef{}: 373 pend_more.def(nm, ll, dd, sig, text, nn, out) 374 case other: 375 St{Pending{kk, nm, ll, dd, text <> sig}, (nn + 1 : U32), [], out} 376 377 # an indented line: more of a pending header, a constructor of an open 378 # type, or nothing 379 def indent(mode: Mode, text: String, +nn: U32, out: List<&2, Item>) -> St: 380 match mode: 381 case Pending{k, nm, l, d, sig}: 382 pend_more(k, nm, l, d, sig, text, nn, out) 383 case InType{}: 384 St{InType{}, (nn + 1 : U32), [], add(ctor(text, nn), out)} 385 case Top{}: 386 St{Top{}, (nn + 1 : U32), [], out} 387 388 # a column-0 line that starts nothing: `) -> T:` closing a def's header that 389 # broke across lines; anywhere else it is junk, and ends what was pending 390 def stray(+mode: Mode, text: String, +nn: U32, +out: List<&2, Item>) -> St: 391 match mode: 392 case Pending{IDef{}, nm, l, d, sig}: 393 pend_more(IDef{}, nm, l, d, sig, text, nn, out) 394 case m: 395 St{Top{}, (nn + 1 : U32), [], flush(m, out)} 396 397 # a finished def, or a header that is still open 398 def start_def.on(colon: Bool, +ss: String, +nn: U32, doc: String, out: List<&2, Item>) -> St: 399 match colon: 400 case True{}: 401 St{Top{}, (nn + 1 : U32), [], Item{IDef{}, def_name(ss), nn, ss, doc, ""} <> out} 402 case False{}: 403 St{Pending{IDef{}, def_name(ss), nn, doc, [ss]}, (nn + 1 : U32), [], out} 404 405 # a `def` line: an item at once when it ends in `:`, else a pending header 406 def start_def(+ss: String, +nn: U32, doc: String, out: List<&2, Item>) -> St: 407 start_def.on(ends_colon(ss), ss, nn, doc, out) 408 409 # a comment continues the block above it only at the top level 410 def doc_above(mode: Mode, doc: List<&2, String>) -> List<&2, String>: 411 match mode: 412 case Top{}: 413 doc 414 case other: 415 [] 416 417 # a law header, pending the block under it 418 def step.law(+ss: String, +nn: U32, doc: String, mode: Mode, out: List<&2, Item>) -> St: 419 St{Pending{ILaw{}, name(String.drop(ss, 4n)), nn, doc, [ss]}, (nn + 1 : U32), [], flush(mode, out)} 420 421 # a type header, and the block that follows it 422 def step.ty(+ss: String, +nn: U32, doc: String, mode: Mode, out: List<&2, Item>) -> St: 423 St{InType{}, (nn + 1 : U32), [], 424 Item{IType{}, name(String.drop(ss, 5n)), nn, ss, doc, ""} <> flush(mode, out)} 425 426 def step.go(cls: Cls, +mode: Mode, ss: String, +nn: U32, doc: List<&2, String>, out: List<&2, Item>) -> St: 427 match cls: 428 case LBlank{}: 429 St{Top{}, (nn + 1 : U32), [], flush(mode, out)} 430 case LComment{t}: 431 St{Top{}, (nn + 1 : U32), t <> doc_above(mode, doc), flush(mode, out)} 432 case LIndent{t}: 433 indent(mode, t, nn, out) 434 case LImport{}: 435 St{Top{}, (nn + 1 : U32), [], add(import_item(ss, nn, doc_text(doc)), flush(mode, out))} 436 case LDef{}: 437 start_def(ss, nn, doc_text(doc), flush(mode, out)) 438 case LLaw{}: 439 step.law(ss, nn, doc_text(doc), mode, out) 440 case LType{}: 441 step.ty(ss, nn, doc_text(doc), mode, out) 442 case LOther{}: 443 stray(mode, ss, nn, out) 444 445 # the class and the line, applied to the outline 446 def step.use(pp: Cls & String, mode: Mode, +nn: U32, doc: List<&2, String>, out: List<&2, Item>) -> St: 447 (cls, s) = pp 448 step.go(cls, mode, s, nn, doc, out) 449 450 # a line inside a string: more of a pending header, else nothing 451 def step.text.go(mode: Mode, ss: String, +nn: U32, doc: List<&2, String>, out: List<&2, Item>) -> St: 452 match mode: 453 case Pending{k, nm, l, d, sig}: 454 St{Pending{k, nm, l, d, ss <> sig}, (nn + 1 : U32), doc, out} 455 case m: 456 St{m, (nn + 1 : U32), doc, out} 457 458 # a line inside a string, applied to the outline 459 def step.text(ss: String, st: St) -> St: 460 St{mode, n, doc, out} = st 461 step.text.go(mode, ss, n, doc, out) 462 463 # a line of code, by its class 464 def step.code(ss: String, st: St) -> St: 465 St{mode, n, doc, out} = st 466 step.use(classify.hold(ss), mode, n, doc, out) 467 468 # one line, by its class, or as text when within says it starts inside a string 469 def step(within: Bool, ss: String, st: St) -> St: 470 match within: 471 case True{}: 472 step.text(ss, st) 473 case False{}: 474 step.code(ss, st) 475 476 # one flag per line break in a text, onto acc (reversed), each str: whether 477 # the text is a string's 478 def breaks(tt: String, +str: Bool, +acc: List<&2, Bool>) -> List<&2, Bool>: 479 match tt: 480 case SNil{}: 481 acc 482 case SCon{h, t}: 483 breaks(t, str, Bool.pick(List<&2, Bool>, U32.is_eq(Char.to_u32(h), 10), str <> acc, acc)) # '\n' 484 485 def inside.go(toks: List<&2, Lex.Tok>, acc: List<&2, Bool>) -> List<&2, Bool>: 486 match toks: 487 case Nil{}: 488 acc 489 case Con{Lex.Tok{k, t, l, c}, rest}: 490 inside.go(rest, breaks(t, Lex.is_str(k), acc)) 491 492 # one flag per line, in order: does it start inside a string? Line 0 does 493 # not; the line after a line break does when the break sits in a string token 494 def inside(toks: List<&2, Lex.Tok>) -> List<&2, Bool>: 495 False{} <> List.reverse(&2, Bool, inside.go(toks, [])) 496 497 # does the line start inside a string? ins are the flags of it and the lines 498 # after it; none left is no 499 def inside.hit(ins: List<&2, Bool>) -> Bool: 500 match ins: 501 case Nil{}: 502 False{} 503 case Con{h, t}: 504 h 505 506 # the flags of the lines after this one 507 def inside.rest(ins: List<&2, Bool>) -> List<&2, Bool>: 508 match ins: 509 case Nil{}: 510 Nil{} 511 case Con{h, t}: 512 t 513 514 # every line, in order; ins flags the lines ahead that start inside a string 515 def run(lines: List<&2, String>, st: St, +ins: List<&2, Bool>) -> St: 516 match lines: 517 case Nil{}: 518 st 519 case Con{l, t}: 520 run(t, step(inside.hit(ins), l, st), inside.rest(ins)) 521 522 # the items once the lines are over, a pending header flushed 523 def finish(st: St) -> List<&2, Item>: 524 St{mode, n, doc, out} = st 525 List.reverse(&2, Item, flush(mode, out)) 526 527 # the top-level items of a source whose tokens are already lexed, in order 528 def of_tokens(text: String, toks: List<&2, Lex.Tok>) -> List<&2, Item>: 529 finish(run(String.lines(text), St{Top{}, 0, [], []}, inside(toks))) 530 531 # the top-level items of a source, in order 532 def items(+text: String) -> List<&2, Item>: 533 of_tokens(text, Lex.tokens(text)) 534 535 # lookups 536 # ------- 537 538 # an import? 539 def is_import(kk: ItemKind) -> Bool: 540 match kk: 541 case IImport{}: 542 True{} 543 case other: 544 False{} 545 546 # the item of that name; imports are not names of the module 547 def find(items: List<&2, Item>, +name: String) -> Maybe<&2, Item>: 548 match items: 549 case Nil{}: 550 None{} 551 case Con{Item{+k, +nm, l, s, d, p}, t}: 552 Lazy.stop(Maybe<&2, Item>, Bool.and(Bool.not(is_import(k)), String.eq(nm, name)), 553 Some{Item{k, nm, l, s, d, p}}, _u => find(t, name)) 554 555 # the path behind an import alias 556 def import_path(items: List<&2, Item>, +alias: String) -> Maybe<&2, String>: 557 match items: 558 case Nil{}: 559 None{} 560 case Con{Item{k, nm, l, s, d, p}, t}: 561 Lazy.stop(Maybe<&2, String>, Bool.and(is_import(k), String.eq(nm, alias)), 562 Some{p}, _u => import_path(t, alias)) 563 564 # is the name among these? 565 def has(names: List<&2, String>, +name: String) -> Bool: 566 match names: 567 case Nil{}: 568 False{} 569 case Con{h, t}: 570 Lazy.or_else(String.eq(h, name), _u => has(t, name)) 571 572 def starting.go(items: List<&2, Item>, +prefix: String, +qual: String, +seen: List<&2, String>) -> List<&2, Item>: 573 match items: 574 case Nil{}: 575 Nil{} 576 case Con{Item{+k, +nm, l, s, d, p}, t}: 577 +take = Bool.and(Bool.and(Bool.not(is_import(k)), String.starts_with(nm, prefix)), Bool.not(has(seen, nm))) 578 # one recursive call: Bool.pick evaluates both of its branches 579 +rest = starting.go(t, prefix, qual, Bool.pick(List<&2, String>, take, nm <> seen, seen)) 580 Bool.pick(List<&2, Item>, take, Item{k, qual ++ nm, l, s, d, p} <> rest, rest) 581 582 # the named items whose name starts with prefix, each renamed qual ++ name (the 583 # text to insert). A law and the def that fills it share a name: the first 584 # stands for both. 585 def starting(items: List<&2, Item>, prefix: String, qual: String) -> List<&2, Item>: 586 starting.go(items, prefix, qual, []) 587 588 # the import aliases that start with prefix (`import Base` has none) 589 def aliases(items: List<&2, Item>, +prefix: String) -> List<&2, Item>: 590 match items: 591 case Nil{}: 592 Nil{} 593 case Con{Item{+k, +nm, l, s, d, +p}, t}: 594 +rest = aliases(t, prefix) 595 Bool.pick(List<&2, Item>, 596 Bool.and(Bool.and(is_import(k), String.starts_with(nm, prefix)), Bool.not(String.eq(nm, p))), 597 Item{k, nm, l, s, d, p} <> rest, rest)