https://github.com/akkartik/mu/blob/main/124next-token.subx
   1 # Some tokenization primitives.
   2 
   3 == code
   4 #   instruction                     effective address                                                   register    displacement    immediate
   5 # . op          subop               mod             rm32          base        index         scale       r32
   6 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
   7 
   8 # extract the next run of characters that are different from a given 'delimiter' (skipping multiple delimiters if necessary)
   9 # on reaching end of file, return an empty interval
  10 next-token-from-slice:  # start: (addr byte), end: (addr byte), delimiter: byte, out: (addr slice)
  11     # . prologue
  12     55/push-ebp
  13     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
  14     # . save registers
  15     50/push-eax
  16     51/push-ecx
  17     52/push-edx
  18     57/push-edi
  19     # ecx = end
  20     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
  21     # edx = delimiter
  22     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0x10/disp8      .                 # copy *(ebp+16) to edx
  23     # edi = out
  24     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0x14/disp8      .                 # copy *(ebp+20) to edi
  25     # eax = skip-chars-matching-in-slice(start, end, delimiter)
  26     # . . push args
  27     52/push-edx
  28     51/push-ecx
  29     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
  30     # . . call
  31     e8/call  skip-chars-matching-in-slice/disp32
  32     # . . discard args
  33     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
  34     # out->start = eax
  35     89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           0/r32/eax   .               .                 # copy eax to *edi
  36     # eax = skip-chars-not-matching-in-slice(eax, end, delimiter)
  37     # . . push args
  38     52/push-edx
  39     51/push-ecx
  40     50/push-eax
  41     # . . call
  42     e8/call  skip-chars-not-matching-in-slice/disp32
  43     # . . discard args
  44     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
  45     # out->end = eax
  46     89/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edi+4)
  47     # . restore registers
  48     5f/pop-to-edi
  49     5a/pop-to-edx
  50     59/pop-to-ecx
  51     58/pop-to-eax
  52     # . epilogue
  53     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
  54     5d/pop-to-ebp
  55     c3/return
  56 
  57 test-next-token-from-slice:
  58     # . prologue
  59     55/push-ebp
  60     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
  61     # (eax..ecx) = "  ab"
  62     b8/copy-to-eax  "  ab"/imm32
  63     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
  64     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
  65     05/add-to-eax  4/imm32
  66     # var out/edi: slice
  67     68/push  0/imm32/end
  68     68/push  0/imm32/start
  69     89/copy                         3/mod/direct    7/rm32/edi    .           .             .           4/r32/esp   .               .                 # copy esp to edi
  70     # next-token-from-slice(eax, ecx, 0x20/space, out)
  71     # . . push args
  72     57/push-edi
  73     68/push  0x20/imm32
  74     51/push-ecx
  75     50/push-eax
  76     # . . call
  77     e8/call  next-token-from-slice/disp32
  78     # . . discard args
  79     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
  80     # out->start should be at the 'a'
  81     # . check-ints-equal(out->start - in->start, 2, msg)
  82     # . . push args
  83     68/push  "F - test-next-token-from-slice: start"/imm32
  84     68/push  2/imm32
  85     # . . push out->start - in->start
  86     8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # copy *edi to ecx
  87     2b/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract eax from ecx
  88     51/push-ecx
  89     # . . call
  90     e8/call  check-ints-equal/disp32
  91     # . . discard args
  92     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
  93     # out->end should be after the 'b'
  94     # check-ints-equal(out->end - in->start, 4, msg)
  95     # . . push args
  96     68/push  "F - test-next-token-from-slice: end"/imm32
  97     68/push  4/imm32
  98     # . . push out->end - in->start
  99     8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(edi+4) to ecx
 100     2b/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract eax from ecx
 101     51/push-ecx
 102     # . . call
 103     e8/call  check-ints-equal/disp32
 104     # . . discard args
 105     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 106     # . epilogue
 107     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 108     5d/pop-to-ebp
 109     c3/return
 110 
 111 test-next-token-from-slice-Eof:
 112     # . prologue
 113     55/push-ebp
 114     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 115     # var out/edi: slice
 116     68/push  0/imm32/end
 117     68/push  0/imm32/start
 118     89/copy                         3/mod/direct    7/rm32/edi    .           .             .           4/r32/esp   .               .                 # copy esp to edi
 119     # next-token-from-slice(0, 0, 0x20/space, out)
 120     # . . push args
 121     57/push-edi
 122     68/push  0x20/imm32
 123     68/push  0/imm32
 124     68/push  0/imm32
 125     # . . call
 126     e8/call  next-token-from-slice/disp32
 127     # . . discard args
 128     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
 129     # out should be empty
 130     # . check-ints-equal(out->end - out->start, 0, msg)
 131     # . . push args
 132     68/push  "F - test-next-token-from-slice-Eof"/imm32
 133     68/push  0/imm32
 134     # . . push out->start - in->start
 135     8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(edi+4) to ecx
 136     2b/subtract                     0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # subtract *edi from ecx
 137     51/push-ecx
 138     # . . call
 139     e8/call  check-ints-equal/disp32
 140     # . . discard args
 141     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 142     # . epilogue
 143     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 144     5d/pop-to-ebp
 145     c3/return
 146 
 147 test-next-token-from-slice-nothing:
 148     # . prologue
 149     55/push-ebp
 150     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 151     # (eax..ecx) = "    "
 152     b8/copy-to-eax  "    "/imm32
 153     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
 154     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
 155     05/add-to-eax  4/imm32
 156     # var out/edi: slice
 157     68/push  0/imm32/end
 158     68/push  0/imm32/start
 159     89/copy                         3/mod/direct    7/rm32/edi    .           .             .           4/r32/esp   .               .                 # copy esp to edi
 160     # next-token-from-slice(in, 0x20/space, out)
 161     # . . push args
 162     57/push-edi
 163     68/push  0x20/imm32
 164     51/push-ecx
 165     50/push-eax
 166     # . . call
 167     e8/call  next-token-from-slice/disp32
 168     # . . discard args
 169     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
 170     # out should be empty
 171     # . check-ints-equal(out->end - out->start, 0, msg)
 172     # . . push args
 173     68/push  "F - test-next-token-from-slice-Eof"/imm32
 174     68/push  0/imm32
 175     # . . push out->start - in->start
 176     8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(edi+4) to ecx
 177     2b/subtract                     0/mod/indirect  7/rm32/edi    .           .             .           1/r32/ecx   .               .                 # subtract *edi from ecx
 178     51/push-ecx
 179     # . . call
 180     e8/call  check-ints-equal/disp32
 181     # . . discard args
 182     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 183     # . epilogue
 184     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 185     5d/pop-to-ebp
 186     c3/return
 187 
 188 skip-chars-matching:  # in: (addr stream byte), delimiter: byte
 189     # . prologue
 190     55/push-ebp
 191     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 192     # . save registers
 193     50/push-eax
 194     51/push-ecx
 195     52/push-edx
 196     53/push-ebx
 197     56/push-esi
 198     # esi = in
 199     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 200     # ecx = in->read
 201     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
 202     # ebx = in->write
 203     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           3/r32/ebx   .               .                 # copy *esi to ebx
 204     # edx = delimiter
 205     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
 206 $skip-chars-matching:loop:
 207     # if (in->read >= in->write) break
 208     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # compare ecx with ebx
 209     7d/jump-if->=  $skip-chars-matching:end/disp8
 210     # eax = in->data[in->read]
 211     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
 212     8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
 213     # if (eax != delimiter) break
 214     39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax and edx
 215     75/jump-if-!=  $skip-chars-matching:end/disp8
 216     # ++in->read
 217     41/increment-ecx
 218     eb/jump  $skip-chars-matching:loop/disp8
 219 $skip-chars-matching:end:
 220     # persist in->read
 221     89/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy ecx to *(esi+4)
 222     # . restore registers
 223     5e/pop-to-esi
 224     5b/pop-to-ebx
 225     5a/pop-to-edx
 226     59/pop-to-ecx
 227     58/pop-to-eax
 228     # . epilogue
 229     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 230     5d/pop-to-ebp
 231     c3/return
 232 
 233 test-skip-chars-matching:
 234     # setup
 235     # . clear-stream(_test-stream)
 236     # . . push args
 237     68/push  _test-stream/imm32
 238     # . . call
 239     e8/call  clear-stream/disp32
 240     # . . discard args
 241     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 242     # write(_test-stream, "  ab")
 243     # . . push args
 244     68/push  "  ab"/imm32
 245     68/push  _test-stream/imm32
 246     # . . call
 247     e8/call  write/disp32
 248     # . . discard args
 249     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 250     # skip-chars-matching(_test-stream, 0x20/space)
 251     # . . push args
 252     68/push  0x20/imm32
 253     68/push  _test-stream/imm32
 254     # . . call
 255     e8/call  skip-chars-matching/disp32
 256     # . . discard args
 257     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 258     # check-ints-equal(_test-stream->read, 2, msg)
 259     # . . push args
 260     68/push  "F - test-skip-chars-matching"/imm32
 261     68/push  2/imm32
 262     # . . push *_test-stream->read
 263     b8/copy-to-eax  _test-stream/imm32
 264     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
 265     # . . call
 266     e8/call  check-ints-equal/disp32
 267     # . . discard args
 268     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 269     # end
 270     c3/return
 271 
 272 test-skip-chars-matching-none:
 273     # setup
 274     # . clear-stream(_test-stream)
 275     # . . push args
 276     68/push  _test-stream/imm32
 277     # . . call
 278     e8/call  clear-stream/disp32
 279     # . . discard args
 280     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 281     # write(_test-stream, "ab")
 282     # . . push args
 283     68/push  "ab"/imm32
 284     68/push  _test-stream/imm32
 285     # . . call
 286     e8/call  write/disp32
 287     # . . discard args
 288     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 289     # skip-chars-matching(_test-stream, 0x20/space)
 290     # . . push args
 291     68/push  0x20/imm32
 292     68/push  _test-stream/imm32
 293     # . . call
 294     e8/call  skip-chars-matching/disp32
 295     # . . discard args
 296     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 297     # check-ints-equal(_test-stream->read, 0, msg)
 298     # . . push args
 299     68/push  "F - test-skip-chars-matching-none"/imm32
 300     68/push  0/imm32
 301     # . . push *_test-stream->read
 302     b8/copy-to-eax  _test-stream/imm32
 303     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
 304     # . . call
 305     e8/call  check-ints-equal/disp32
 306     # . . discard args
 307     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 308     # end
 309     c3/return
 310 
 311 skip-chars-matching-whitespace:  # in: (addr stream byte)
 312     # . prologue
 313     55/push-ebp
 314     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 315     # . save registers
 316     50/push-eax
 317     51/push-ecx
 318     53/push-ebx
 319     56/push-esi
 320     # esi = in
 321     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 322     # ecx = in->read
 323     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
 324     # ebx = in->write
 325     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           3/r32/ebx   .               .                 # copy *esi to ebx
 326 $skip-chars-matching-whitespace:loop:
 327     # if (in->read >= in->write) break
 328     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # compare ecx with ebx
 329     7d/jump-if->=  $skip-chars-matching-whitespace:end/disp8
 330     # eax = in->data[in->read]
 331     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
 332     8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
 333     # if (eax == ' ') goto body
 334     3d/compare-eax-and  0x20/imm32/space
 335     74/jump-if-=  $skip-chars-matching-whitespace:body/disp8
 336     # if (eax == '\n') goto body
 337     3d/compare-eax-and  0x0a/imm32/newline
 338     74/jump-if-=  $skip-chars-matching-whitespace:body/disp8
 339     # if (eax == '\t') goto body
 340     3d/compare-eax-and  0x09/imm32/tab
 341     74/jump-if-=  $skip-chars-matching-whitespace:body/disp8
 342     # if (eax != '\r') break
 343     3d/compare-eax-and  0x0d/imm32/cr
 344     75/jump-if-!=  $skip-chars-matching-whitespace:end/disp8
 345 $skip-chars-matching-whitespace:body:
 346     # ++in->read
 347     41/increment-ecx
 348     eb/jump  $skip-chars-matching-whitespace:loop/disp8
 349 $skip-chars-matching-whitespace:end:
 350     # persist in->read
 351     89/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy ecx to *(esi+4)
 352     # . restore registers
 353     5e/pop-to-esi
 354     5b/pop-to-ebx
 355     59/pop-to-ecx
 356     58/pop-to-eax
 357     # . epilogue
 358     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 359     5d/pop-to-ebp
 360     c3/return
 361 
 362 test-skip-chars-matching-whitespace:
 363     # setup
 364     # . clear-stream(_test-stream)
 365     # . . push args
 366     68/push  _test-stream/imm32
 367     # . . call
 368     e8/call  clear-stream/disp32
 369     # . . discard args
 370     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 371     # write(_test-stream, " \nab")
 372     # . . push args
 373     68/push  " \nab"/imm32
 374     68/push  _test-stream/imm32
 375     # . . call
 376     e8/call  write/disp32
 377     # . . discard args
 378     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 379     # skip-chars-matching-whitespace(_test-stream)
 380     # . . push args
 381     68/push  _test-stream/imm32
 382     # . . call
 383     e8/call  skip-chars-matching-whitespace/disp32
 384     # . . discard args
 385     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 386     # check-ints-equal(_test-stream->read, 2, msg)
 387     # . . push args
 388     68/push  "F - test-skip-chars-matching-whitespace"/imm32
 389     68/push  2/imm32
 390     # . . push *_test-stream->read
 391     b8/copy-to-eax  _test-stream/imm32
 392     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
 393     # . . call
 394     e8/call  check-ints-equal/disp32
 395     # . . discard args
 396     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 397     # end
 398     c3/return
 399 
 400 # minor fork of 'skip-chars-matching'
 401 skip-chars-not-matching:  # in: (addr stream byte), delimiter: byte
 402     # . prologue
 403     55/push-ebp
 404     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 405     # . save registers
 406     50/push-eax
 407     51/push-ecx
 408     52/push-edx
 409     53/push-ebx
 410     56/push-esi
 411     # esi = in
 412     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 413     # ecx = in->read
 414     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
 415     # ebx = in->write
 416     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           3/r32/ebx   .               .                 # copy *esi to ebx
 417     # edx = delimiter
 418     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0xc/disp8       .                 # copy *(ebp+12) to edx
 419 $skip-chars-not-matching:loop:
 420     # if (in->read >= in->write) break
 421     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # compare ecx with ebx
 422     7d/jump-if->=  $skip-chars-not-matching:end/disp8
 423     # eax = in->data[in->read]
 424     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
 425     8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
 426     # if (eax == delimiter) break
 427     39/compare                      3/mod/direct    0/rm32/eax    .           .             .           2/r32/edx   .               .                 # compare eax and edx
 428     74/jump-if-=  $skip-chars-not-matching:end/disp8
 429     # ++in->read
 430     41/increment-ecx
 431     eb/jump  $skip-chars-not-matching:loop/disp8
 432 $skip-chars-not-matching:end:
 433     # persist in->read
 434     89/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy ecx to *(esi+4)
 435     # . restore registers
 436     5e/pop-to-esi
 437     5b/pop-to-ebx
 438     5a/pop-to-edx
 439     59/pop-to-ecx
 440     58/pop-to-eax
 441     # . epilogue
 442     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 443     5d/pop-to-ebp
 444     c3/return
 445 
 446 test-skip-chars-not-matching:
 447     # setup
 448     # . clear-stream(_test-stream)
 449     # . . push args
 450     68/push  _test-stream/imm32
 451     # . . call
 452     e8/call  clear-stream/disp32
 453     # . . discard args
 454     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 455     # write(_test-stream, "ab ")
 456     # . . push args
 457     68/push  "ab "/imm32
 458     68/push  _test-stream/imm32
 459     # . . call
 460     e8/call  write/disp32
 461     # . . discard args
 462     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 463     # skip-chars-not-matching(_test-stream, 0x20/space)
 464     # . . push args
 465     68/push  0x20/imm32
 466     68/push  _test-stream/imm32
 467     # . . call
 468     e8/call  skip-chars-not-matching/disp32
 469     # . . discard args
 470     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 471     # check-ints-equal(_test-stream->read, 2, msg)
 472     # . . push args
 473     68/push  "F - test-skip-chars-not-matching"/imm32
 474     68/push  2/imm32
 475     # . . push *_test-stream->read
 476     b8/copy-to-eax  _test-stream/imm32
 477     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
 478     # . . call
 479     e8/call  check-ints-equal/disp32
 480     # . . discard args
 481     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 482     # end
 483     c3/return
 484 
 485 test-skip-chars-not-matching-none:
 486     # setup
 487     # . clear-stream(_test-stream)
 488     # . . push args
 489     68/push  _test-stream/imm32
 490     # . . call
 491     e8/call  clear-stream/disp32
 492     # . . discard args
 493     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 494     # write(_test-stream, " ab")
 495     # . . push args
 496     68/push  " ab"/imm32
 497     68/push  _test-stream/imm32
 498     # . . call
 499     e8/call  write/disp32
 500     # . . discard args
 501     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 502     # skip-chars-not-matching(_test-stream, 0x20/space)
 503     # . . push args
 504     68/push  0x20/imm32
 505     68/push  _test-stream/imm32
 506     # . . call
 507     e8/call  skip-chars-not-matching/disp32
 508     # . . discard args
 509     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 510     # check-ints-equal(_test-stream->read, 0, msg)
 511     # . . push args
 512     68/push  "F - test-skip-chars-not-matching-none"/imm32
 513     68/push  0/imm32
 514     # . . push *_test-stream->read
 515     b8/copy-to-eax  _test-stream/imm32
 516     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
 517     # . . call
 518     e8/call  check-ints-equal/disp32
 519     # . . discard args
 520     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 521     # end
 522     c3/return
 523 
 524 test-skip-chars-not-matching-all:
 525     # setup
 526     # . clear-stream(_test-stream)
 527     # . . push args
 528     68/push  _test-stream/imm32
 529     # . . call
 530     e8/call  clear-stream/disp32
 531     # . . discard args
 532     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 533     # write(_test-stream, "ab")
 534     # . . push args
 535     68/push  "ab"/imm32
 536     68/push  _test-stream/imm32
 537     # . . call
 538     e8/call  write/disp32
 539     # . . discard args
 540     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 541     # skip-chars-not-matching(_test-stream, 0x20/space)
 542     # . . push args
 543     68/push  0x20/imm32
 544     68/push  _test-stream/imm32
 545     # . . call
 546     e8/call  skip-chars-not-matching/disp32
 547     # . . discard args
 548     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 549     # check-ints-equal(_test-stream->read, 2, msg)
 550     # . . push args
 551     68/push  "F - test-skip-chars-not-matching-all"/imm32
 552     68/push  2/imm32
 553     # . . push *_test-stream->read
 554     b8/copy-to-eax  _test-stream/imm32
 555     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
 556     # . . call
 557     e8/call  check-ints-equal/disp32
 558     # . . discard args
 559     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 560     # end
 561     c3/return
 562 
 563 skip-chars-not-matching-whitespace:  # in: (addr stream byte)
 564     # . prologue
 565     55/push-ebp
 566     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 567     # . save registers
 568     50/push-eax
 569     51/push-ecx
 570     53/push-ebx
 571     56/push-esi
 572     # esi = in
 573     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 574     # ecx = in->read
 575     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
 576     # ebx = in->write
 577     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           3/r32/ebx   .               .                 # copy *esi to ebx
 578 $skip-chars-not-matching-whitespace:loop:
 579     # if (in->read >= in->write) break
 580     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # compare ecx with ebx
 581     7d/jump-if->=  $skip-chars-not-matching-whitespace:end/disp8
 582     # eax = in->data[in->read]
 583     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
 584     8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
 585     # if (eax == ' ') break
 586     3d/compare-eax-and  0x20/imm32/space
 587     74/jump-if-=  $skip-chars-not-matching-whitespace:end/disp8
 588     # if (eax == '\n') break
 589     3d/compare-eax-and  0x0a/imm32/newline
 590     74/jump-if-=  $skip-chars-not-matching-whitespace:end/disp8
 591     # if (eax == '\t') break
 592     3d/compare-eax-and  0x09/imm32/tab
 593     74/jump-if-=  $skip-chars-not-matching-whitespace:end/disp8
 594     # if (eax == '\r') break
 595     3d/compare-eax-and  0x0d/imm32/cr
 596     74/jump-if-=  $skip-chars-not-matching-whitespace:end/disp8
 597     # ++in->read
 598     41/increment-ecx
 599     eb/jump  $skip-chars-not-matching-whitespace:loop/disp8
 600 $skip-chars-not-matching-whitespace:end:
 601     # persist in->read
 602     89/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy ecx to *(esi+4)
 603     # . restore registers
 604     5e/pop-to-esi
 605     5b/pop-to-ebx
 606     59/pop-to-ecx
 607     58/pop-to-eax
 608     # . epilogue
 609     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 610     5d/pop-to-ebp
 611     c3/return
 612 
 613 test-skip-chars-not-matching-whitespace:
 614     # setup
 615     # . clear-stream(_test-stream)
 616     # . . push args
 617     68/push  _test-stream/imm32
 618     # . . call
 619     e8/call  clear-stream/disp32
 620     # . . discard args
 621     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 622     # write(_test-stream, "ab\n")
 623     # . . push args
 624     68/push  "ab\n"/imm32
 625     68/push  _test-stream/imm32
 626     # . . call
 627     e8/call  write/disp32
 628     # . . discard args
 629     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 630     # skip-chars-not-matching-whitespace(_test-stream)
 631     # . . push args
 632     68/push  _test-stream/imm32
 633     # . . call
 634     e8/call  skip-chars-not-matching-whitespace/disp32
 635     # . . discard args
 636     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 637     # check-ints-equal(_test-stream->read, 2, msg)
 638     # . . push args
 639     68/push  "F - test-skip-chars-not-matching-whitespace"/imm32
 640     68/push  2/imm32
 641     # . . push *_test-stream->read
 642     b8/copy-to-eax  _test-stream/imm32
 643     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
 644     # . . call
 645     e8/call  check-ints-equal/disp32
 646     # . . discard args
 647     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 648     # end
 649     c3/return
 650 
 651 skip-chars-matching-in-slice:  # curr: (addr byte), end: (addr byte), delimiter: byte -> curr/eax: (addr byte)
 652     # . prologue
 653     55/push-ebp
 654     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 655     # . save registers
 656     51/push-ecx
 657     52/push-edx
 658     53/push-ebx
 659     # eax = curr
 660     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
 661     # ecx = end
 662     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
 663     # edx = delimiter
 664     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0x10/disp8       .                 # copy *(ebp+16) to edx
 665     # var c/ebx: byte = 0
 666     31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
 667 $skip-chars-matching-in-slice:loop:
 668     # if (curr >= end) break
 669     39/compare                      3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare eax with ecx
 670     73/jump-if-addr>=  $skip-chars-matching-in-slice:end/disp8
 671     # c = *curr
 672     8a/copy-byte                    0/mod/indirect  0/rm32/eax    .           .             .           3/r32/BL    .               .                 # copy byte at *eax to BL
 673     # if (c != delimiter) break
 674     39/compare                      3/mod/direct    3/rm32/ebx    .           .             .           2/r32/edx   .               .                 # compare ebx and edx
 675     75/jump-if-!=  $skip-chars-matching-in-slice:end/disp8
 676     # ++curr
 677     40/increment-eax
 678     eb/jump  $skip-chars-matching-in-slice:loop/disp8
 679 $skip-chars-matching-in-slice:end:
 680     # . restore registers
 681     5b/pop-to-ebx
 682     5a/pop-to-edx
 683     59/pop-to-ecx
 684     # . epilogue
 685     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 686     5d/pop-to-ebp
 687     c3/return
 688 
 689 test-skip-chars-matching-in-slice:
 690     # (eax..ecx) = "  ab"
 691     b8/copy-to-eax  "  ab"/imm32
 692     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
 693     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
 694     05/add-to-eax  4/imm32
 695     # eax = skip-chars-matching-in-slice(eax, ecx, 0x20/space)
 696     # . . push args
 697     68/push  0x20/imm32/space
 698     51/push-ecx
 699     50/push-eax
 700     # . . call
 701     e8/call  skip-chars-matching-in-slice/disp32
 702     # . . discard args
 703     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 704     # check-ints-equal(ecx-eax, 2, msg)
 705     # . . push args
 706     68/push  "F - test-skip-chars-matching-in-slice"/imm32
 707     68/push  2/imm32
 708     # . . push ecx-eax
 709     29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
 710     51/push-ecx
 711     # . . call
 712     e8/call  check-ints-equal/disp32
 713     # . . discard args
 714     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 715     # end
 716     c3/return
 717 
 718 test-skip-chars-matching-in-slice-none:
 719     # (eax..ecx) = "ab"
 720     b8/copy-to-eax  "ab"/imm32
 721     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
 722     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
 723     05/add-to-eax  4/imm32
 724     # eax = skip-chars-matching-in-slice(eax, ecx, 0x20/space)
 725     # . . push args
 726     68/push  0x20/imm32/space
 727     51/push-ecx
 728     50/push-eax
 729     # . . call
 730     e8/call  skip-chars-matching-in-slice/disp32
 731     # . . discard args
 732     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 733     # check-ints-equal(ecx-eax, 2, msg)
 734     # . . push args
 735     68/push  "F - test-skip-chars-matching-in-slice-none"/imm32
 736     68/push  2/imm32
 737     # . . push ecx-eax
 738     29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
 739     51/push-ecx
 740     # . . call
 741     e8/call  check-ints-equal/disp32
 742     # . . discard args
 743     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 744     # end
 745     c3/return
 746 
 747 skip-chars-matching-whitespace-in-slice:  # curr: (addr byte), end: (addr byte) -> curr/eax: (addr byte)
 748     # . prologue
 749     55/push-ebp
 750     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 751     # . save registers
 752     51/push-ecx
 753     53/push-ebx
 754     # eax = curr
 755     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
 756     # ecx = end
 757     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
 758     # var c/ebx: byte = 0
 759     31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
 760 $skip-chars-matching-whitespace-in-slice:loop:
 761     # if (curr >= end) break
 762     39/compare                      3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare eax with ecx
 763     0f 83/jump-if-addr>=  $skip-chars-matching-in-slice:end/disp32
 764     # c = *curr
 765     8a/copy-byte                    0/mod/indirect  0/rm32/eax    .           .             .           3/r32/BL    .               .                 # copy byte at *eax to BL
 766     # if (c == ' ') goto body
 767     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x20/imm32/space  # compare ebx
 768     74/jump-if-=  $skip-chars-matching-whitespace-in-slice:body/disp8
 769     # if (c == '\n') goto body
 770     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x0a/imm32/newline  # compare ebx
 771     74/jump-if-=  $skip-chars-matching-whitespace-in-slice:body/disp8
 772     # if (c == '\t') goto body
 773     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x09/imm32/tab    # compare ebx
 774     74/jump-if-=  $skip-chars-matching-whitespace-in-slice:body/disp8
 775     # if (c != '\r') break
 776     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x0d/imm32/cr     # compare ebx
 777     75/jump-if-!=  $skip-chars-matching-whitespace-in-slice:end/disp8
 778 $skip-chars-matching-whitespace-in-slice:body:
 779     # ++curr
 780     40/increment-eax
 781     eb/jump  $skip-chars-matching-whitespace-in-slice:loop/disp8
 782 $skip-chars-matching-whitespace-in-slice:end:
 783     # . restore registers
 784     5b/pop-to-ebx
 785     59/pop-to-ecx
 786     # . epilogue
 787     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 788     5d/pop-to-ebp
 789     c3/return
 790 
 791 test-skip-chars-matching-whitespace-in-slice:
 792     # (eax..ecx) = " \nab"
 793     b8/copy-to-eax  " \nab"/imm32
 794     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
 795     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
 796     05/add-to-eax  4/imm32
 797     # eax = skip-chars-matching-whitespace-in-slice(eax, ecx)
 798     # . . push args
 799     51/push-ecx
 800     50/push-eax
 801     # . . call
 802     e8/call  skip-chars-matching-whitespace-in-slice/disp32
 803     # . . discard args
 804     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 805     # check-ints-equal(ecx-eax, 2, msg)
 806     # . . push args
 807     68/push  "F - test-skip-chars-matching-whitespace-in-slice"/imm32
 808     68/push  2/imm32
 809     # . . push ecx-eax
 810     29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
 811     51/push-ecx
 812     # . . call
 813     e8/call  check-ints-equal/disp32
 814     # . . discard args
 815     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 816     # end
 817     c3/return
 818 
 819 # minor fork of 'skip-chars-matching-in-slice'
 820 skip-chars-not-matching-in-slice:  # curr: (addr byte), end: (addr byte), delimiter: byte -> curr/eax: (addr byte)
 821     # . prologue
 822     55/push-ebp
 823     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 824     # . save registers
 825     51/push-ecx
 826     52/push-edx
 827     53/push-ebx
 828     # eax = curr
 829     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
 830     # ecx = end
 831     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
 832     # edx = delimiter
 833     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           2/r32/edx   0x10/disp8       .                 # copy *(ebp+16) to edx
 834     # var c/ebx: byte = 0
 835     31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
 836 $skip-chars-not-matching-in-slice:loop:
 837     # if (curr >= end) break
 838     39/compare                      3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare eax with ecx
 839     73/jump-if-addr>=  $skip-chars-not-matching-in-slice:end/disp8
 840     # c = *curr
 841     8a/copy-byte                    0/mod/indirect  0/rm32/eax    .           .             .           3/r32/BL    .               .                 # copy byte at *eax to BL
 842     # if (c == delimiter) break
 843     39/compare                      3/mod/direct    3/rm32/ebx    .           .             .           2/r32/edx   .               .                 # compare ebx and edx
 844     74/jump-if-=  $skip-chars-not-matching-in-slice:end/disp8
 845     # ++curr
 846     40/increment-eax
 847     eb/jump  $skip-chars-not-matching-in-slice:loop/disp8
 848 $skip-chars-not-matching-in-slice:end:
 849     # . restore registers
 850     5b/pop-to-ebx
 851     5a/pop-to-edx
 852     59/pop-to-ecx
 853     # . epilogue
 854     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 855     5d/pop-to-ebp
 856     c3/return
 857 
 858 test-skip-chars-not-matching-in-slice:
 859     # (eax..ecx) = "ab "
 860     b8/copy-to-eax  "ab "/imm32
 861     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
 862     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
 863     05/add-to-eax  4/imm32
 864     # eax = skip-chars-not-matching-in-slice(eax, ecx, 0x20/space)
 865     # . . push args
 866     68/push  0x20/imm32/space
 867     51/push-ecx
 868     50/push-eax
 869     # . . call
 870     e8/call  skip-chars-not-matching-in-slice/disp32
 871     # . . discard args
 872     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 873     # check-ints-equal(ecx-eax, 1, msg)
 874     # . . push args
 875     68/push  "F - test-skip-chars-not-matching-in-slice"/imm32
 876     68/push  1/imm32
 877     # . . push ecx-eax
 878     29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
 879     51/push-ecx
 880     # . . call
 881     e8/call  check-ints-equal/disp32
 882     # . . discard args
 883     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 884     # end
 885     c3/return
 886 
 887 test-skip-chars-not-matching-in-slice-none:
 888     # (eax..ecx) = " ab"
 889     b8/copy-to-eax  " ab"/imm32
 890     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
 891     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
 892     05/add-to-eax  4/imm32
 893     # eax = skip-chars-not-matching-in-slice(eax, ecx, 0x20/space)
 894     # . . push args
 895     68/push  0x20/imm32/space
 896     51/push-ecx
 897     50/push-eax
 898     # . . call
 899     e8/call  skip-chars-not-matching-in-slice/disp32
 900     # . . discard args
 901     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 902     # check-ints-equal(ecx-eax, 3, msg)
 903     # . . push args
 904     68/push  "F - test-skip-chars-not-matching-in-slice-none"/imm32
 905     68/push  3/imm32
 906     # . . push ecx-eax
 907     29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
 908     51/push-ecx
 909     # . . call
 910     e8/call  check-ints-equal/disp32
 911     # . . discard args
 912     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 913     # end
 914     c3/return
 915 
 916 test-skip-chars-not-matching-in-slice-all:
 917     # (eax..ecx) = "ab"
 918     b8/copy-to-eax  "ab"/imm32
 919     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
 920     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
 921     05/add-to-eax  4/imm32
 922     # eax = skip-chars-not-matching-in-slice(eax, ecx, 0x20/space)
 923     # . . push args
 924     68/push  0x20/imm32/space
 925     51/push-ecx
 926     50/push-eax
 927     # . . call
 928     e8/call  skip-chars-not-matching-in-slice/disp32
 929     # . . discard args
 930     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 931     # check-ints-equal(ecx-eax, 0, msg)
 932     # . . push args
 933     68/push  "F - test-skip-chars-not-matching-in-slice-all"/imm32
 934     68/push  0/imm32
 935     # . . push ecx-eax
 936     29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
 937     51/push-ecx
 938     # . . call
 939     e8/call  check-ints-equal/disp32
 940     # . . discard args
 941     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 942     # end
 943     c3/return
 944 
 945 skip-chars-not-matching-whitespace-in-slice:  # curr: (addr byte), end: (addr byte) -> curr/eax: (addr byte)
 946     # . prologue
 947     55/push-ebp
 948     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 949     # . save registers
 950     51/push-ecx
 951     53/push-ebx
 952     # eax = curr
 953     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
 954     # ecx = end
 955     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   0xc/disp8       .                 # copy *(ebp+12) to ecx
 956     # var c/ebx: byte = 0
 957     31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
 958 $skip-chars-not-matching-whitespace-in-slice:loop:
 959     # if (curr >= end) break
 960     39/compare                      3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # compare eax with ecx
 961     0f 83/jump-if-addr>=  $skip-chars-not-matching-in-slice:end/disp32
 962     # c = *curr
 963     8a/copy-byte                    0/mod/indirect  0/rm32/eax    .           .             .           3/r32/BL    .               .                 # copy byte at *eax to BL
 964     # if (c == ' ') break
 965     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x20/imm32/space  # compare ebx
 966     74/jump-if-=  $skip-chars-not-matching-whitespace-in-slice:end/disp8
 967     # if (c == '\n') break
 968     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x0a/imm32/newline  # compare ebx
 969     74/jump-if-=  $skip-chars-not-matching-whitespace-in-slice:end/disp8
 970     # if (c == '\t') break
 971     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x09/imm32/tab    # compare ebx
 972     74/jump-if-=  $skip-chars-not-matching-whitespace-in-slice:end/disp8
 973     # if (c == '\r') break
 974     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x0d/imm32/cr     # compare ebx
 975     74/jump-if-=  $skip-chars-not-matching-whitespace-in-slice:end/disp8
 976     # ++curr
 977     40/increment-eax
 978     eb/jump  $skip-chars-not-matching-whitespace-in-slice:loop/disp8
 979 $skip-chars-not-matching-whitespace-in-slice:end:
 980     # . restore registers
 981     5b/pop-to-ebx
 982     59/pop-to-ecx
 983     # . epilogue
 984     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 985     5d/pop-to-ebp
 986     c3/return
 987 
 988 test-skip-chars-not-matching-whitespace-in-slice:
 989     # (eax..ecx) = "ab\n"
 990     b8/copy-to-eax  "ab\n"/imm32
 991     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
 992     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
 993     05/add-to-eax  4/imm32
 994     # eax = skip-chars-not-matching-whitespace-in-slice(eax, ecx)
 995     # . . push args
 996     51/push-ecx
 997     50/push-eax
 998     # . . call
 999     e8/call  skip-chars-not-matching-whitespace-in-slice/disp32
1000     # . . discard args
1001     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1002     # check-ints-equal(ecx-eax, 1, msg)
1003     # . . push args
1004     68/push  "F - test-skip-chars-not-matching-whitespace-in-slice"/imm32
1005     68/push  1/imm32
1006     # . . push ecx-eax
1007     29/subtract                     3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract eax from ecx
1008     51/push-ecx
1009     # . . call
1010     e8/call  check-ints-equal/disp32
1011     # . . discard args
1012     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1013     # end
1014     c3/return
1015 
1016 # update line->read to end of string literal surrounded by double quotes
1017 # line->read must start out at a double-quote
1018 skip-string:  # line: (addr stream byte)
1019     # . prologue
1020     55/push-ebp
1021     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1022     # . save registers
1023     50/push-eax
1024     51/push-ecx
1025     52/push-edx
1026     # ecx = line
1027     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
1028     # eax = skip-string-in-slice(&line->data[line->read], &line->data[line->write])
1029     # . . push &line->data[line->write]
1030     8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .                         2/r32/edx   8/disp8         .                 # copy *(ecx+8) to edx
1031     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   0xc/disp8       .                 # copy ecx+edx+12 to edx
1032     52/push-edx
1033     # . . push &line->data[line->read]
1034     8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .                         2/r32/edx   4/disp8         .                 # copy *(ecx+4) to edx
1035     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ecx  2/index/edx   .           2/r32/edx   0xc/disp8       .                 # copy ecx+edx+12 to edx
1036     52/push-edx
1037     # . . call
1038     e8/call  skip-string-in-slice/disp32
1039     # . . discard args
1040     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1041     # line->read = eax - line->data
1042     29/subtract                     3/mod/direct    0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # subtract ecx from eax
1043     2d/subtract-from-eax  0xc/imm32
1044     89/copy                         1/mod/*+disp8   1/rm32/ecx    .           .                         0/r32/eax   4/disp8         .                 # copy eax to *(ecx+4)
1045 $skip-string:end:
1046     # . restore registers
1047     5a/pop-to-edx
1048     59/pop-to-ecx
1049     58/pop-to-eax
1050     # . epilogue
1051     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1052     5d/pop-to-ebp
1053     c3/return
1054 
1055 test-skip-string:
1056     # . prologue
1057     55/push-ebp
1058     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1059     # setup
1060     # . clear-stream(_test-stream)
1061     # . . push args
1062     68/push  _test-stream/imm32
1063     # . . call
1064     e8/call  clear-stream/disp32
1065     # . . discard args
1066     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1067     # . write(_test-stream, "\"abc\" def")
1068     # .                   indices:  0123 45
1069     # . . push args
1070     68/push  "\"abc\" def"/imm32
1071     68/push  _test-stream/imm32
1072     # . . call
1073     e8/call  write/disp32
1074     # . . discard args
1075     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1076     # precondition: line->read == 0
1077     # . . push args
1078     68/push  "F - test-skip-string/precondition"/imm32
1079     68/push  0/imm32
1080     b8/copy-to-eax  _test-stream/imm32
1081     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
1082     # . . call
1083     e8/call  check-ints-equal/disp32
1084     # . . discard args
1085     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1086     # skip-string(_test-stream)
1087     # . . push args
1088     68/push  _test-stream/imm32
1089     # . . call
1090     e8/call  skip-string/disp32
1091     # . . discard args
1092     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1093     # check-ints-equal(line->read, 5, msg)
1094     # . . push args
1095     68/push  "F - test-skip-string"/imm32
1096     68/push  5/imm32
1097     b8/copy-to-eax  _test-stream/imm32
1098     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
1099     # . . call
1100     e8/call  check-ints-equal/disp32
1101     # . . discard args
1102     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1103     # . epilogue
1104     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1105     5d/pop-to-ebp
1106     c3/return
1107 
1108 test-skip-string-ignores-spaces:
1109     # . prologue
1110     55/push-ebp
1111     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1112     # setup
1113     # . clear-stream(_test-stream)
1114     # . . push args
1115     68/push  _test-stream/imm32
1116     # . . call
1117     e8/call  clear-stream/disp32
1118     # . . discard args
1119     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1120     # . write(_test-stream, "\"a b\"/yz")
1121     # .                   indices:  0123 45
1122     # . . push args
1123     68/push  "\"a b\"/yz"/imm32
1124     68/push  _test-stream/imm32
1125     # . . call
1126     e8/call  write/disp32
1127     # . . discard args
1128     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1129     # precondition: line->read == 0
1130     # . . push args
1131     68/push  "F - test-skip-string-ignores-spaces/precondition"/imm32
1132     68/push  0/imm32
1133     b8/copy-to-eax  _test-stream/imm32
1134     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
1135     # . . call
1136     e8/call  check-ints-equal/disp32
1137     # . . discard args
1138     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1139     # skip-string(_test-stream)
1140     # . . push args
1141     68/p
//: Phase 3: Start running a loaded and transformed recipe.
//:
//:   The process of running Mu code:
//:     load -> transform -> run
//:
//: So far we've seen recipes as lists of instructions, and instructions point
//: at other recipes. To kick things off Mu needs to know how to run certain
//: 'primitive' recipes. That will then give the ability to run recipes
//: containing these primitives.
//:
//: This layer defines a skeleton with just two primitive recipes: IDLE which
//: does nothing, and COPY, which can copy numbers from one memory location to
//: another. Later layers will add more primitives.

void test_copy_literal() {
  run(
      "def main [\n"
      "  1:num <- copy 23\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "run: {1: \"number\"} <- copy {23: \"literal\"}\n"
      "mem: storing 23 in location 1\n"
  );
}

void test_copy() {
  run(
      "def main [\n"
      "  1:num <- copy 23\n"
      "  2:num <- copy 1:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "run: {2: \"number\"} <- copy {1: \"number\"}\n"
      "mem: location 1 is 23\n"
      "mem: storing 23 in location 2\n"
  );
}

void test_copy_multiple() {
  run(
      "def main [\n"
      "  1:num, 2:num <- copy 23, 24\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 23 in location 1\n"
      "mem: storing 24 in location 2\n"
  );
}

:(before "End Types")
// Book-keeping while running a recipe.
//: Later layers will replace this to support running multiple routines at once.
struct routine {
  recipe_ordinal running_recipe;
  int running_step_index;
  routine(recipe_ordinal r) :running_recipe(r), running_step_index(0) {}
  bool completed() const;
  const vector<instruction>& steps() const;
};

:(before "End Globals")
routine* Current_routine = NULL;
:(before "End Reset")
Current_routine = NULL;

:(code)
void run(const recipe_ordinal r) {
  routine rr(r);
  Current_routine = &rr;
  run_current_routine();
  Current_routine = NULL;
}

void run_current_routine() {
  while (should_continue_running(Current_routine)) {  // beware: may modify Current_routine
    // Running One Instruction
    if (current_instruction().is_label) { ++current_step_index();  continue; }
    trace(Callstack_depth, "run") << to_string(current_instruction()) << end();
//?     if (Foo) cerr << "run: " << to_string(current_instruction()) << '\n';
    if (get_or_insert(Memory, 0) != 0) {
      raise << "something wrote to location 0; this should never happen\n" << end();
      put(Memory, 0, 0);
    }
    // read all ingredients from memory, each potentially spanning multiple locations
    vector<vector<double> > ingredients;
    if (should_copy_ingredients()) {
      for (int i = 0;  i < SIZE(current_instruction().ingredients);  ++i)
        ingredients.push_back(read_memory(current_instruction().ingredients.at(i)));
    }
    // instructions below will write to 'products'
    vector<vector<double> > products;
    //: This will be a large switch that later layers will often insert cases
    //: into. Never call 'continue' within it. Instead, we'll explicitly
    //: control which of the following stages after the switch we run for each
    //: instruction.
    bool write_products = true;
    bool fall_through_to_next_instruction = true;
    switch (current_instruction().operation) {
      // Primitive Recipe Implementations
      case COPY: {
        copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin()));
        break;
      }
      // End Primitive Recipe Implementations
      default: {
        raise << "not a primitive op: " << current_instruction().operation << '\n' << end();
      }
    }
    //: used by a later layer
    if (write_products) {
      if (SIZE(products) < SIZE(current_instruction().products)) {
        raise << SIZE(products) << " vs " << SIZE(current_instruction().products) << ": failed to write to all products in '" << to_original_string(current_instruction()) << "'\n" << end();
      }
      else {
        for (int i = 0;  i < SIZE(current_instruction().products);  ++i) {
          // Writing Instruction Product(i)
          write_memory(current_instruction().products.at(i), products.at(i));
        }
      }
    }
    // End Running One Instruction
    if (fall_through_to_next_instruction)
      ++current_step_index();
  }
  stop_running_current_routine:;
}

//: Helpers for managing trace depths
//:
//: We're going to use trace depths primarily to segment code running at
//: different frames of the call stack. This will make it easy for the trace
//: browser to collapse over entire calls.
//:
//: The entire map of possible depths is as follows:
//:
//: Errors will be depth 0.
//: Mu 'applications' will be able to use depths 1-99 as they like.
//: Primitive statements will occupy 100 and up to Max_depth, organized by
//: stack frames.
:(before "End Globals")
extern const int Initial_callstack_depth = 100;
int Callstack_depth = Initial_callstack_depth;
:(before "End Reset")
Callstack_depth = Initial_callstack_depth;

//: Other helpers for the VM.

:(code)
//: hook replaced in a later layer
bool should_continue_running(const routine* current_routine) {
  assert(current_routine == Current_routine);  // argument passed in just to make caller readable above
  return !Current_routine->completed();
}

bool should_copy_ingredients() {
  // End should_copy_ingredients Special-cases
  return true;
}

bool is_mu_scalar(reagent/*copy*/ r) {
  return is_mu_scalar(r.type);
}
bool is_mu_scalar(const type_tree* type) {
  if (!type) return false;
  if (is_mu_address(type)) return false;
  if (!type->atom) return false;
  if (is_literal(type))
    return type->name != "literal-string";
  return size_of(type) == 1;
}

bool is_mu_address(reagent/*copy*/ r) {
  // End Preprocess is_mu_address(reagent r)
  return is_mu_address(r.type);
}
bool is_mu_address(const type_tree* type) {
  if (!type) return false;
  if (is_literal(type)) return false;
  if (type->atom) return false;
  if (!type->left->atom) {
    raise << "invalid type " << to_string(type) << '\n' << end();
    return false;
  }
  return type->left->value == Address_type_ordinal;
}

//: Some helpers.
//: Important that they return references into the current routine.

//: hook replaced in a later layer
int& current_step_index() {
  return Current_routine->running_step_index;
}

//: hook replaced in a later layer
recipe_ordinal currently_running_recipe() {
  return Current_routine->running_recipe;
}

//: hook replaced in a later layer
const string& current_recipe_name() {
  return get(Recipe, Current_routine->running_recipe).name;
}

//: hook replaced in a later layer
const recipe& current_recipe() {
  return get(Recipe, Current_routine->running_recipe);
}

//: hook replaced in a later layer
const instruction& current_instruction() {
  return get(Recipe, Current_routine->running_recipe).steps.at(Current_routine->running_step_index);
}

//: hook replaced in a later layer
bool routine::completed() const {
  return running_step_index >= SIZE(get(Recipe, running_recipe).steps);
}

//: hook replaced in a later layer
const vector<instruction>& routine::steps() const {
  return get(Recipe, running_recipe).steps;
}

//:: Startup flow

:(before "End Mu Prelude")
load_file_or_directory("core.mu");
//? DUMP("");
//? exit(0);

//: Step 2: load any .mu files provided at the commandline
:(before "End Commandline Parsing")
// Check For .mu Files
if (argc > 1) {
  // skip argv[0]
  ++argv;
  --argc;
  while (argc > 0) {
    // ignore argv past '--'; that's commandline args for 'main'
    if (string(*argv) == "--") break;
    if (starts_with(*argv, "--"))
      cerr << "treating " << *argv << " as a file rather than an option\n";
    load_file_or_directory(*argv);
    --argc;
    ++argv;
  }
  if (Run_tests) Recipe.erase(get(Recipe_ordinal, "main"));
}
transform_all();
//? cerr << to_original_string(get(Type_ordinal, "editor")) << '\n';
//? cerr << to_original_string(get(Recipe, get(Recipe_ordinal, "event-loop"))) << '\n';
//? DUMP("");
//? exit(0);
if (trace_contains_errors()) return 1;
if (Trace_stream && Run_tests) {
  // We'll want a trace per test. Clear the trace.
  delete Trace_stream;
  Trace_stream = NULL;
}
save_snapshots();

//: Step 3: if we aren't running tests, locate a recipe called 'main' and
//: start running it.
:(before "End Main")
if (!Run_tests && contains_key(Recipe_ordinal, "main") && contains_key(Recipe, get(Recipe_ordinal, "main"))) {
  // Running Main
  reset();
  trace(2, "run") << "=== Starting to run" << end();
  assert(Num_calls_to_transform_all == 1);
  run_main(argc, argv);
}

:(code)
void run_main(int argc, char* argv[]) {
  recipe_ordinal r = get(Recipe_ordinal, "main");
  if (r) run(r);
}

void load_file_or_directory(string filename) {
  if (is_directory(filename)) {
    load_all(filename);
    return;
  }
  ifstream fin(filename.c_str());
  if (!fin) {
    cerr << "no such file '" << filename << "'\n" << end();  // don't raise, just warn. just in case it's just a name for a test to run.
    return;
  }
  trace(2, "load") << "=== " << filename << end();
  load(fin);
  fin.close();
}

bool is_directory(string path) {
  struct stat info;
  if (stat(path.c_str(), &info)) return false;  // error
  return info.st_mode & S_IFDIR;
}

void load_all(string dir) {
  dirent** files;
  int num_files = scandir(dir.c_str(), &files, NULL, alphasort);
  for (int i = 0;  i < num_files;  ++i) {
    string curr_file = files[i]->d_name;
    if (isdigit(curr_file.at(0)) && ends_with(curr_file, ".mu"))
      load_file_or_directory(dir+'/'+curr_file);
    free(files[i]);
    files[i] = NULL;
  }
  free(files);
}

bool ends_with(const string& s, const string& pat) {
  for (string::const_reverse_iterator p = s.rbegin(), q = pat.rbegin();  q != pat.rend();  ++p, ++q) {
    if (p == s.rend()) return false;  // pat too long
    if (*p != *q) return false;
  }
  return true;
}

:(before "End Includes")
#include <dirent.h>
#include <sys/stat.h>

//:: Reading from memory, writing to memory.

:(code)
vector<double> read_memory(reagent/*copy*/ x) {
  // Begin Preprocess read_memory(x)
  vector<double> result;
  if (x.name == "null") result.push_back(/*alloc id*/0);
  if (is_literal(x)) {
    result.push_back(x.value);
    return result;
  }
  // End Preprocess read_memory(x)
  int size = size_of(x);
  for (int offset = 0;  offset < size;  ++offset) {
    double val = get_or_insert(Memory, x.value+offset);
    trace(Callstack_depth+1, "mem") << "location " << x.value+offset << " is " << no_scientific(val) << end();
    result.push_back(val);
  }
  return result;
}

void write_memory(reagent/*copy*/ x, const vector<double>& data) {
  assert(Current_routine);  // run-time only
  // Begin Preprocess write_memory(x, data)
  if (!x.type) {
    raise << "can't write to '" << to_string(x) << "'; no type\n" << end();
    return;
  }
  if (is_dummy(x)) return;
  if (is_literal(x)) return;
  // End Preprocess write_memory(x, data)
  if (x.value == 0) {
    raise << "can't write to location 0 in '" << to_original_string(current_instruction()) << "'\n" << end();
    return;
  }
  if (size_mismatch(x, data)) {
    raise << maybe(current_recipe_name()) << "size mismatch in storing to '" << x.original_string << "' (" << size_of(x) << " vs " << SIZE(data) << ") at '" << to_original_string(current_instruction()) << "'\n" << end();
    return;
  }
  // End write_memory(x) Special-cases
  for (int offset = 0;  offset < SIZE(data);  ++offset) {
    assert(x.value+offset > 0);
    trace(Callstack_depth+1, "mem") << "storing " << no_scientific(data.at(offset)) << " in location " << x.value+offset << end();
//?     if (Foo) cerr << "mem: storing " << no_scientific(data.at(offset)) << " in location " << x.value+offset << '\n';
    put(Memory, x.value+offset, data.at(offset));
  }
}

:(code)
int size_of(const reagent& r) {
  if (!r.type) return 0;
  // End size_of(reagent r) Special-cases
  return size_of(r.type);
}
int size_of(const type_tree* type) {
  if (!type) return 0;
  if (type->atom) {
    if (type->value == -1) return 1;  // error value, but we'll raise it elsewhere
    if (type->value == 0) return 1;
    // End size_of(type) Atom Special-cases
  }
  else {
    if (!type->left->atom) {
      raise << "invalid type " << to_string(type) << '\n' << end();
      return 0;
    }
    if (type->left->value == Address_type_ordinal) return 2;  // address and alloc id
    // End size_of(type) Non-atom Special-cases
  }
  // End size_of(type) Special-cases
  return 1;
}

bool size_mismatch(const reagent& x, const vector<double>& data) {
  if (!x.type) return true;
  // End size_mismatch(x) Special-cases
//?   if (size_of(x) != SIZE(data)) cerr << size_of(x) << " vs " << SIZE(data) << '\n';
  return size_of(x) != SIZE(data);
}

bool is_literal(const reagent& r) {
  return is_literal(r.type);
}
bool is_literal(const type_tree* type) {
  if (!type) return false;
  if (!type->atom) return false;
  return type->value == 0;
}

bool scalar(const vector<int>& x) {
  return SIZE(x) == 1;
}
bool scalar(const vector<double>& x) {
  return SIZE(x) == 1;
}

// helper for tests
void run(const string& form) {
  vector<recipe_ordinal> tmp = load(form);
  transform_all();
  if (tmp.empty()) return;
  if (trace_contains_errors()) return;
  // if a test defines main, it probably wants to start there regardless of
  // definition order
  if (contains_key(Recipe, get(Recipe_ordinal, "main")))
    run(get(Recipe_ordinal, "main"));
  else
    run(tmp.front());
}

void test_run_label() {
  run(
      "def main [\n"
      "  +foo\n"
      "  1:num <- copy 23\n"
      "  2:num <- copy 1:num\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "run: {1: \"number\"} <- copy {23: \"literal\"}\n"
      "run: {2: \"number\"} <- copy {1: \"number\"}\n"
  );
  CHECK_TRACE_DOESNT_CONTAIN("run: +foo");
}

void test_run_dummy() {
  run(
      "def main [\n"
      "  _ <- copy 0\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "run: _ <- copy {0: \"literal\"}\n"
  );
}

void test_run_null() {
  run(
      "def main [\n"
      "  1:&:num <- copy null\n"
      "]\n"
  );
}

void test_write_to_0_disallowed() {
  Hide_errors = true;
  run(
      "def main [\n"
      "  0:num <- copy 34\n"
      "]\n"
  );
  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 34 in location 0");
}

//: Mu is robust to various combinations of commas and spaces. You just have
//: to put spaces around the '<-'.

void test_comma_without_space() {
  run(
      "def main [\n"
      "  1:num, 2:num <- copy 2,2\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 2 in location 1\n"
  );
}

void test_space_without_comma() {
  run(
      "def main [\n"
      "  1:num, 2:num <- copy 2 2\n"
      "]\n"
  );
  CHECK_TRACE_CONTENTS(
      "mem: storing 2 in location 1\n"
  );
}

void test_comma_before_space() {
  run(
      "def main [\n"
      "  1:num, 2:num <- copy 2, 2\n"