https://github.com/akkartik/mu/blob/master/apps/dquotes.subx
   1 # Translate literal strings within double quotes.
   2 # Replace them with references to new variables in the data segment.
   3 #
   4 # To run:
   5 #   $ ./subx translate init.linux 0*.subx apps/subx-params.subx apps/dquotes.subx  -o apps/dquotes
   6 #   $ cat x
   7 #   == code
   8 #   ab "cd ef"/imm32
   9 #   $ cat x  |./subx run apps/dquotes
  10 #   == code
  11 #   ab __string1/imm32
  12 #   == data
  13 #   __string1:
  14 #     5/imm32
  15 #     0x63/c 0x64/d 0x20/  0x65/e 0x66/f
  16 
  17 == code
  18 #   instruction                     effective address                                                   register    displacement    immediate
  19 # . op          subop               mod             rm32          base        index         scale       r32
  20 # . 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
  21 
  22 Entry:  # run tests if necessary, convert stdin if not
  23     # . prologue
  24     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
  25 
  26     # initialize heap
  27     # . Heap = new-segment(Heap-size)
  28     # . . push args
  29     68/push  Heap/imm32
  30     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Heap-size/disp32                  # push *Heap-size
  31     # . . call
  32     e8/call  new-segment/disp32
  33     # . . discard args
  34     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
  35 
  36     # - if argc > 1 and argv[1] == "test", then return run-tests()
  37     # if (argc <= 1) goto interactive
  38     81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0/disp8         1/imm32           # compare *ebp
  39     7e/jump-if-<=  $subx-dquotes-main:interactive/disp8
  40     # if (!kernel-string-equal?(argv[1], "test")) goto interactive
  41     # . eax = kernel-string-equal?(argv[1], "test")
  42     # . . push args
  43     68/push  "test"/imm32
  44     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
  45     # . . call
  46     e8/call  kernel-string-equal?/disp32
  47     # . . discard args
  48     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
  49     # . if (eax == false) goto interactive
  50     3d/compare-eax-and  0/imm32/false
  51     74/jump-if-=  $subx-dquotes-main:interactive/disp8
  52     # run-tests()
  53     e8/call  run-tests/disp32
  54     # syscall(exit, *Num-test-failures)
  55     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/ebx   Num-test-failures/disp32          # copy *Num-test-failures to ebx
  56     eb/jump  $subx-dquotes-main:end/disp8
  57 $subx-dquotes-main:interactive:
  58     # - otherwise convert stdin
  59     # var ed/eax: exit-descriptor
  60     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # subtract from esp
  61     89/copy                         3/mod/direct    0/rm32/eax    .           .             .           4/r32/esp   .               .                 # copy esp to eax
  62     # configure ed to really exit()
  63     # . ed->target = 0
  64     c7          0/subop/copy        0/mod/direct    0/rm32/eax    .           .             .           .           .               0/imm32           # copy to *eax
  65     # subx-dquotes(Stdin, Stdout, Stderr, ed)
  66     # . . push args
  67     50/push-eax/ed
  68     68/push  Stderr/imm32
  69     68/push  Stdout/imm32
  70     68/push  Stdin/imm32
  71     # . . call
  72     e8/call  subx-dquotes/disp32
  73     # . . discard args
  74     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x10/imm32        # add to esp
  75     # syscall(exit, 0)
  76     bb/copy-to-ebx  0/imm32
  77 $subx-dquotes-main:end:
  78     b8/copy-to-eax  1/imm32/exit
  79     cd/syscall  0x80/imm8
  80 
  81 # conceptual hierarchy within a line:
  82 #   line = words separated by ' ', maybe followed by comment starting with '#'
  83 #   word = datum until '/', then 0 or more metadata separated by '/'
  84 
  85 subx-dquotes:  # in: (addr buffered-file), out: (addr buffered-file)
  86     # pseudocode:
  87     #   var line: (stream byte 512)
  88     #   var new-data-segment: (handle stream byte) = new-stream(Heap, Segment-size, 1)
  89     #
  90     #   write(new-data-segment, "== data\n")
  91     #      # TODO: When it was originally written dquotes ran before assort, so
  92     #      # it assumes lots of segment headers, and emits a new segment of its
  93     #      # own. We've since had to reorder the phases (see the explanation
  94     #      # for a.assort2 in translate_subx). We could clean up a.assort2 if we
  95     #      # conditionally emit the previous line. But this would require
  96     #      # teaching dquotes to parse segment headers, so maybe that's not
  97     #      # best..
  98     #
  99     #   while true
 100     #     clear-stream(line)
 101     #     read-line-buffered(in, line)
 102     #     if (line->write == 0) break               # end of file
 103     #     while true
 104     #       var word-slice = next-word-or-string(line)
 105     #       if slice-empty?(word-slice)             # end of line
 106     #         break
 107     #       if slice-starts-with?(word-slice, "#")  # comment
 108     #         continue
 109     #       if slice-starts-with?(word-slice, '"')  # string literal <== what we're here for
 110     #         process-string-literal(word-slice, out, new-data-segment)
 111     #       else
 112     #         write-slice-buffered(out, word-slice)
 113     #       write(out, " ")
 114     #     write(out, "\n\n")
 115     #   write-stream-data(out, new-data-segment)
 116     #   flush(out)
 117     #
 118     # . prologue
 119     55/push-ebp
 120     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 121     # . save registers
 122     50/push-eax
 123     51/push-ecx
 124     52/push-edx
 125     53/push-ebx
 126     56/push-esi
 127     57/push-edi
 128     # var line/ecx: (stream byte 512)
 129     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0x200/imm32       # subtract from esp
 130     68/push  0x200/imm32/length
 131     68/push  0/imm32/read
 132     68/push  0/imm32/write
 133     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
 134     # var word-slice/edx: slice
 135     68/push  0/imm32/end
 136     68/push  0/imm32/start
 137     89/copy                         3/mod/direct    2/rm32/edx    .           .             .           4/r32/esp   .               .                 # copy esp to edx
 138     # new-data-segment/edi = new-stream(Heap, Segment-size, 1)
 139     # . eax = new-stream(Heap, Segment-size, 1)
 140     # . . push args
 141     68/push  1/imm32
 142     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Segment-size/disp32               # push *Segment-size
 143     68/push  Heap/imm32
 144     # . . call
 145     e8/call  new-stream/disp32
 146     # . . discard args
 147     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 148     # . edi = eax
 149     89/copy                         3/mod/direct    7/rm32/edi    .           .             .           0/r32/eax   .               .                 # copy eax to edi
 150     # write(new-data-segment, "== data\n")
 151     # . . push args
 152     68/push  "== data\n"/imm32
 153     57/push-edi
 154     # . . call
 155     e8/call  write/disp32
 156     # . . discard args
 157     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 158 $subx-dquotes:line-loop:
 159     # clear-stream(line)
 160     # . . push args
 161     51/push-ecx
 162     # . . call
 163     e8/call  clear-stream/disp32
 164     # . . discard args
 165     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 166     # read-line-buffered(in, line)
 167     # . . push args
 168     51/push-ecx
 169     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
 170     # . . call
 171     e8/call  read-line-buffered/disp32
 172     # . . discard args
 173     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 174 $subx-dquotes:check0:
 175     # if (line->write == 0) break
 176     81          7/subop/compare     0/mod/indirect  1/rm32/ecx    .           .             .           .           .               0/imm32           # compare *ecx
 177     0f 84/jump-if-=  $subx-dquotes:break/disp32
 178 $subx-dquotes:word-loop:
 179     # next-word-or-string(line, word-slice)
 180     # . . push args
 181     52/push-edx
 182     51/push-ecx
 183     # . . call
 184     e8/call  next-word-or-string/disp32
 185     # . . discard args
 186     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 187 $subx-dquotes:check1:
 188     # if (slice-empty?(word-slice)) break
 189     # . eax = slice-empty?(word-slice)
 190     # . . push args
 191     52/push-edx
 192     # . . call
 193     e8/call  slice-empty?/disp32
 194     # . . discard args
 195     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 196     # . if (eax != false) break
 197     3d/compare-eax-and  0/imm32/false
 198     0f 85/jump-if-!=  $subx-dquotes:next-line/disp32
 199 $subx-dquotes:check-for-comment:
 200     # if (slice-starts-with?(word-slice, "#")) continue
 201     # . var start/esi: (addr byte) = word-slice->start
 202     8b/copy                         0/mod/indirect  2/rm32/edx    .           .             .           6/r32/esi   .               .                 # copy *edx to esi
 203     # . var c/eax: byte = *start
 204     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
 205     8a/copy-byte                    0/mod/indirect  6/rm32/esi    .           .             .           0/r32/AL    .               .                 # copy byte at *esi to AL
 206     # . if (c == '#') continue
 207     3d/compare-eax-and  0x23/imm32/hash
 208     74/jump-if-=  $subx-dquotes:word-loop/disp8
 209 $subx-dquotes:check-for-string-literal:
 210     # if (slice-starts-with?(word-slice, '"')) continue
 211     3d/compare-eax-and  0x22/imm32/dquote
 212     75/jump-if-!=  $subx-dquotes:regular-word/disp8
 213 $subx-dquotes:string-literal:
 214     # process-string-literal(word-slice, out, new-data-segment)
 215     # . . push args
 216     57/push-edi
 217     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 218     52/push-edx
 219     # . . call
 220     e8/call  process-string-literal/disp32
 221     # . . discard args
 222     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 223     # continue
 224     eb/jump  $subx-dquotes:next-word/disp8
 225 $subx-dquotes:regular-word:
 226     # write-slice-buffered(out, word-slice)
 227     # . . push args
 228     52/push-edx
 229     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 230     # . . call
 231     e8/call  write-slice-buffered/disp32
 232     # . . discard args
 233     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 234     # fall through
 235 $subx-dquotes:next-word:
 236     # write-buffered(out, " ")
 237     # . . push args
 238     68/push  Space/imm32
 239     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 240     # . . call
 241     e8/call  write-buffered/disp32
 242     # . . discard args
 243     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 244     # loop
 245     eb/jump  $subx-dquotes:word-loop/disp8
 246 $subx-dquotes:next-line:
 247     # write-buffered(out, "\n")
 248     # . . push args
 249     68/push  Newline/imm32
 250     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 251     # . . call
 252     e8/call  write-buffered/disp32
 253     # . . discard args
 254     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 255     # loop
 256     e9/jump  $subx-dquotes:line-loop/disp32
 257 $subx-dquotes:break:
 258     # write-stream-data(out, new-data-segment)
 259     # . . push args
 260     57/push-edi
 261     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 262     # . . call
 263     e8/call  write-stream-data/disp32
 264     # . . discard args
 265     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 266     # flush(out)
 267     # . . push args
 268     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 269     # . . call
 270     e8/call  flush/disp32
 271     # . . discard args
 272     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 273 $subx-dquotes:end:
 274     # . reclaim locals
 275     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x214/imm32       # add to esp
 276     # . restore registers
 277     5f/pop-to-edi
 278     5e/pop-to-esi
 279     5b/pop-to-ebx
 280     5a/pop-to-edx
 281     59/pop-to-ecx
 282     58/pop-to-eax
 283     # . epilogue
 284     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 285     5d/pop-to-ebp
 286     c3/return
 287 
 288 # Write out 'string-literal' in a new format to 'out-segment', assign it a new
 289 # label, and write the new label out to 'out'.
 290 process-string-literal:  # string-literal: (addr slice), out: (addr buffered-file), out-segment: (addr stream byte)
 291     # pseudocode:
 292     #   print(out-segment, "_string#{Next-string-literal}:\n")
 293     #   emit-string-literal-data(out-segment, string-literal)
 294     #   print(out, "_string#{Next-string-literal}")
 295     #   emit-metadata(out, string-literal)
 296     #   ++ *Next-string-literal
 297     #
 298     # . prologue
 299     55/push-ebp
 300     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 301     # . save registers
 302     51/push-ecx
 303     # var int32-stream/ecx: (stream byte 10)  # number of decimal digits a 32-bit number can have
 304     81          5/subop/subtract    3/mod/direct    4/rm32/esp    .           .             .           .           .               0xa/imm32         # subtract from esp
 305     68/push  0xa/imm32/decimal-digits-in-32bit-number
 306     68/push  0/imm32/read
 307     68/push  0/imm32/write
 308     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
 309     # print(out-segment, "_string#{Next-string-literal}:\n")
 310     # . write(out-segment, "_string")
 311     # . . push args
 312     68/push  "_string"/imm32
 313     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
 314     # . . call
 315     e8/call  write/disp32
 316     # . . discard args
 317     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 318     # . print-int32-decimal(out-segment, *Next-string-literal)
 319     # . . push args
 320     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-string-literal/disp32        # push *Next-string-literal
 321     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
 322     # . . call
 323     e8/call  print-int32-decimal/disp32
 324     # . . discard args
 325     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 326     # . write(out-segment, ":\n")
 327     # . . push args
 328     68/push  ":\n"/imm32
 329     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
 330     # . . call
 331     e8/call  write/disp32
 332     # . . discard args
 333     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 334     # emit-string-literal-data(out-segment, string-literal)
 335     # . . push args
 336     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
 337     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
 338     # . . call
 339     e8/call  emit-string-literal-data/disp32
 340     # . . discard args
 341     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 342     # write(out-segment, "\n")
 343     # . . push args
 344     68/push  Newline/imm32
 345     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0x10/disp8      .                 # push *(ebp+16)
 346     # . . call
 347     e8/call  write/disp32
 348     # . . discard args
 349     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 350     # print(out, "_string#{Next-string-literal}")
 351     # . write-buffered(out, "_string")
 352     # . . push args
 353     68/push  "_string"/imm32
 354     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 355     # . . call
 356     e8/call  write-buffered/disp32
 357     # . . discard args
 358     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 359     # . print-int32-decimal(int32-stream, *Next-string-literal)
 360     # . . push args
 361     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Next-string-literal/disp32        # push *Next-string-literal
 362     51/push-ecx
 363     # . . call
 364     e8/call  print-int32-decimal/disp32
 365     # . . discard args
 366     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 367     # . write-stream-data(out, int32-stream)
 368     # . . push args
 369     51/push-ecx
 370     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 371     # . . call
 372     e8/call  write-stream-data/disp32
 373     # . . discard args
 374     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 375     # emit-metadata(out, string-literal)
 376     # . . push args
 377     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
 378     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
 379     # . . call
 380     e8/call  emit-metadata/disp32
 381     # . . discard args
 382     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 383     # ++ *Next-string-literal
 384     ff          0/subop/increment   0/mod/indirect  5/rm32/.disp32            .             .           .           Next-string-literal/disp32        # increment *Num-test-failures
 385 $process-string-literal:end:
 386     # . reclaim locals
 387     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0x16/imm32        # add to esp
 388     # . restore registers
 389     59/pop-to-ecx
 390     # . epilogue
 391     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 392     5d/pop-to-ebp
 393     c3/return
 394 
 395 test-subx-dquotes-is-idempotent-by-default:
 396     # . prologue
 397     55/push-ebp
 398     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 399     # setup
 400     # . clear-stream(_test-input-stream)
 401     # . . push args
 402     68/push  _test-input-stream/imm32
 403     # . . call
 404     e8/call  clear-stream/disp32
 405     # . . discard args
 406     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 407     # . clear-stream($_test-input-buffered-file->buffer)
 408     # . . push args
 409     68/push  $_test-input-buffered-file->buffer/imm32
 410     # . . call
 411     e8/call  clear-stream/disp32
 412     # . . discard args
 413     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 414     # . clear-stream(_test-output-stream)
 415     # . . push args
 416     68/push  _test-output-stream/imm32
 417     # . . call
 418     e8/call  clear-stream/disp32
 419     # . . discard args
 420     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 421     # . clear-stream($_test-output-buffered-file->buffer)
 422     # . . push args
 423     68/push  $_test-output-buffered-file->buffer/imm32
 424     # . . call
 425     e8/call  clear-stream/disp32
 426     # . . discard args
 427     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 428     # initialize input (meta comments in parens)
 429     #   # comment 1
 430     #     # comment 2 indented
 431     #   == code 0x1  (new segment)
 432     #   # comment 3 inside a segment
 433     #   1
 434     #                         (empty line)
 435     #   2 3 # comment 4 inline with other contents
 436     #   == data 0x2  (new segment)
 437     #   4 5/imm32
 438     # . write(_test-input-stream, "# comment 1\n")
 439     # . . push args
 440     68/push  "# comment 1\n"/imm32
 441     68/push  _test-input-stream/imm32
 442     # . . call
 443     e8/call  write/disp32
 444     # . . discard args
 445     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 446     # . write(_test-input-stream, "  # comment 2 indented\n")
 447     # . . push args
 448     68/push  "  # comment 2 indented\n"/imm32
 449     68/push  _test-input-stream/imm32
 450     # . . call
 451     e8/call  write/disp32
 452     # . . discard args
 453     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 454     # . write(_test-input-stream, "== code 0x1\n")
 455     # . . push args
 456     68/push  "== code 0x1\n"/imm32
 457     68/push  _test-input-stream/imm32
 458     # . . call
 459     e8/call  write/disp32
 460     # . . discard args
 461     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 462     # . write(_test-input-stream, "# comment 3 inside a segment\n")
 463     # . . push args
 464     68/push  "# comment 3 inside a segment\n"/imm32
 465     68/push  _test-input-stream/imm32
 466     # . . call
 467     e8/call  write/disp32
 468     # . . discard args
 469     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 470     # . write(_test-input-stream, "1\n")
 471     # . . push args
 472     68/push  "1\n"/imm32
 473     68/push  _test-input-stream/imm32
 474     # . . call
 475     e8/call  write/disp32
 476     # . . discard args
 477     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 478     # . write(_test-input-stream, "\n")  # empty line
 479     # . . push args
 480     68/push  Newline/imm32
 481     68/push  _test-input-stream/imm32
 482     # . . call
 483     e8/call  write/disp32
 484     # . . discard args
 485     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 486     # . write(_test-input-stream, "2 3 # comment 4 inline with other contents\n")
 487     # . . push args
 488     68/push  "2 3 # comment 4 inline with other contents\n"/imm32
 489     68/push  _test-input-stream/imm32
 490     # . . call
 491     e8/call  write/disp32
 492     # . . discard args
 493     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 494     # . write(_test-input-stream, "== data 0x2\n")
 495     # . . push args
 496     68/push  "== data 0x2\n"/imm32
 497     68/push  _test-input-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     # . write(_test-input-stream, "4 5/imm32\n")
 503     # . . push args
 504     68/push  "4 5/imm32\n"/imm32
 505     68/push  _test-input-stream/imm32
 506     # . . call
 507     e8/call  write/disp32
 508     # . . discard args
 509     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 510     # subx-dquotes(_test-input-buffered-file, _test-output-buffered-file)
 511     # . . push args
 512     68/push  _test-output-buffered-file/imm32
 513     68/push  _test-input-buffered-file/imm32
 514     # . . call
 515     e8/call  subx-dquotes/disp32
 516     # . . discard args
 517     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 518     # . flush(_test-output-buffered-file)
 519     # . . push args
 520     68/push  _test-output-buffered-file/imm32
 521     # . . call
 522     e8/call  flush/disp32
 523     # . . discard args
 524     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 525     # check output
 526     #     (comment dropped for now)
 527     #     (comment dropped for now)
 528     #   == code 0x1
 529     #     (comment dropped for now)
 530     #   1
 531     #     (comment dropped for now)
 532     #   2 3
 533     #   == data 0x2
 534     #   4 5/imm32
 535     # We don't care right now what exactly happens to comments. Trailing spaces are also minor details.
 536 +-- 26 lines: #?     # dump output ---------------------------------------------------------------------------------------------------------------------------
 562     # . check-next-stream-line-equal(_test-output-stream, "", msg)
 563     # . . push args
 564     68/push  "F - test-subx-dquotes-is-idempotent-by-default/0"/imm32
 565     68/push  ""/imm32
 566     68/push  _test-output-stream/imm32
 567     # . . call
 568     e8/call  check-next-stream-line-equal/disp32
 569     # . . discard args
 570     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 571     # . check-next-stream-line-equal(_test-output-stream, "", msg)
 572     # . . push args
 573     68/push  "F - test-subx-dquotes-is-idempotent-by-default/1"/imm32
 574     68/push  ""/imm32
 575     68/push  _test-output-stream/imm32
 576     # . . call
 577     e8/call  check-next-stream-line-equal/disp32
 578     # . . discard args
 579     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 580     # . check-next-stream-line-equal(_test-output-stream, "== code 0x1 ", msg)
 581     # . . push args
 582     68/push  "F - test-subx-dquotes-is-idempotent-by-default/2"/imm32
 583     68/push  "== code 0x1 "/imm32
 584     68/push  _test-output-stream/imm32
 585     # . . call
 586     e8/call  check-next-stream-line-equal/disp32
 587     # . . discard args
 588     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 589     # . check-next-stream-line-equal(_test-output-stream, "", msg)
 590     # . . push args
 591     68/push  "F - test-subx-dquotes-is-idempotent-by-default/3"/imm32
 592     68/push  ""/imm32
 593     68/push  _test-output-stream/imm32
 594     # . . call
 595     e8/call  check-next-stream-line-equal/disp32
 596     # . . discard args
 597     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 598     # . check-next-stream-line-equal(_test-output-stream, "1 ", msg)
 599     # . . push args
 600     68/push  "F - test-subx-dquotes-is-idempotent-by-default/4"/imm32
 601     68/push  "1 "/imm32
 602     68/push  _test-output-stream/imm32
 603     # . . call
 604     e8/call  check-next-stream-line-equal/disp32
 605     # . . discard args
 606     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 607     # . check-next-stream-line-equal(_test-output-stream, "", msg)
 608     # . . push args
 609     68/push  "F - test-subx-dquotes-is-idempotent-by-default/5"/imm32
 610     68/push  ""/imm32
 611     68/push  _test-output-stream/imm32
 612     # . . call
 613     e8/call  check-next-stream-line-equal/disp32
 614     # . . discard args
 615     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 616     # . check-next-stream-line-equal(_test-output-stream, "2 3 ", msg)
 617     # . . push args
 618     68/push  "F - test-subx-dquotes-is-idempotent-by-default/6"/imm32
 619     68/push  "2 3 "/imm32
 620     68/push  _test-output-stream/imm32
 621     # . . call
 622     e8/call  check-next-stream-line-equal/disp32
 623     # . . discard args
 624     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 625     # . check-next-stream-line-equal(_test-output-stream, "== data 0x2 ", msg)
 626     # . . push args
 627     68/push  "F - test-subx-dquotes-is-idempotent-by-default/7"/imm32
 628     68/push  "== data 0x2 "/imm32
 629     68/push  _test-output-stream/imm32
 630     # . . call
 631     e8/call  check-next-stream-line-equal/disp32
 632     # . . discard args
 633     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 634     # . check-next-stream-line-equal(_test-output-stream, "4 5/imm32 ", msg)
 635     # . . push args
 636     68/push  "F - test-subx-dquotes-is-idempotent-by-default/8"/imm32
 637     68/push  "4 5/imm32 "/imm32
 638     68/push  _test-output-stream/imm32
 639     # . . call
 640     e8/call  check-next-stream-line-equal/disp32
 641     # . . discard args
 642     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 643     # . epilogue
 644     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 645     5d/pop-to-ebp
 646     c3/return
 647 
 648 test-subx-dquotes-processes-string-literals:
 649     # . prologue
 650     55/push-ebp
 651     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 652     # setup
 653     # . clear-stream(_test-input-stream)
 654     # . . push args
 655     68/push  _test-input-stream/imm32
 656     # . . call
 657     e8/call  clear-stream/disp32
 658     # . . discard args
 659     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 660     # . clear-stream($_test-input-buffered-file->buffer)
 661     # . . push args
 662     68/push  $_test-input-buffered-file->buffer/imm32
 663     # . . call
 664     e8/call  clear-stream/disp32
 665     # . . discard args
 666     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 667     # . clear-stream(_test-output-stream)
 668     # . . push args
 669     68/push  _test-output-stream/imm32
 670     # . . call
 671     e8/call  clear-stream/disp32
 672     # . . discard args
 673     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 674     # . clear-stream($_test-output-buffered-file->buffer)
 675     # . . push args
 676     68/push  $_test-output-buffered-file->buffer/imm32
 677     # . . call
 678     e8/call  clear-stream/disp32
 679     # . . discard args
 680     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 681     # initialize input (meta comments in parens)
 682     #   == code  (new segment)
 683     #   1 "a"/x
 684     #   2 "bc"/y
 685     68/push  "== code 0x1\n"/imm32
 686     68/push  _test-input-stream/imm32
 687     # . . call
 688     e8/call  write/disp32
 689     # . . discard args
 690     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 691     # . write(_test-input-stream, "1 \"a\"/x\n")
 692     # . . push args
 693     68/push  "1 \"a\"/x\n"/imm32
 694     68/push  _test-input-stream/imm32
 695     # . . call
 696     e8/call  write/disp32
 697     # . . discard args
 698     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 699     # . write(_test-input-stream, "2 \"bc\"/y\n")
 700     # . . push args
 701     68/push  "2 \"bc\"/y\n"/imm32
 702     68/push  _test-input-stream/imm32
 703     # . . call
 704     e8/call  write/disp32
 705     # . . discard args
 706     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 707     # subx-dquotes(_test-input-buffered-file, _test-output-buffered-file)
 708     # . . push args
 709     68/push  _test-output-buffered-file/imm32
 710     68/push  _test-input-buffered-file/imm32
 711     # . . call
 712     e8/call  subx-dquotes/disp32
 713     # . . discard args
 714     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 715     # . flush(_test-output-buffered-file)
 716     # . . push args
 717     68/push  _test-output-buffered-file/imm32
 718     # . . call
 719     e8/call  flush/disp32
 720     # . . discard args
 721     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 722     # check output
 723     #   == code 0x1
 724     #   1 _string1/x
 725     #   2 _string2/y
 726     #   == data
 727     #   _string1:
 728     #   1/imm32 61/a
 729     #   _string2:
 730     #   2/imm32 62/b 63/c
 731     # We don't care right now what exactly happens to comments. Trailing spaces are also minor details.
 732     #
 733     # Open question: how to make this check more robust.
 734     # We don't actually care what the auto-generated string variables are
 735     # called. We just want to make sure instructions using string literals
 736     # switch to a string variable with the right value.
 737     # (Modifying string literals completely off the radar for now.)
 738 +-- 33 lines: #?     # dump output ---------------------------------------------------------------------------------------------------------------------------
 771     # . check-next-stream-line-equal(_test-output-stream, "== code 0x1 ", msg)
 772     # . . push args
 773     68/push  "F - test-subx-dquotes-processes-string-literals/0"/imm32
 774     68/push  "== code 0x1 "/imm32
 775     68/push  _test-output-stream/imm32
 776     # . . call
 777     e8/call  check-next-stream-line-equal/disp32
 778     # . . discard args
 779     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 780     # . check-next-stream-line-equal(_test-output-stream, "1 _string1/x ", msg)
 781     # . . push args
 782     68/push  "F - test-subx-dquotes-processes-string-literals/1"/imm32
 783     68/push  "1 _string1/x "/imm32
 784     68/push  _test-output-stream/imm32
 785     # . . call
 786     e8/call  check-next-stream-line-equal/disp32
 787     # . . discard args
 788     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 789     # . check-next-stream-line-equal(_test-output-stream, "2 _string2/y ", msg)
 790     # . . push args
 791     68/push  "F - test-subx-dquotes-processes-string-literals/2"/imm32
 792     68/push  "2 _string2/y "/imm32
 793     68/push  _test-output-stream/imm32
 794     # . . call
 795     e8/call  check-next-stream-line-equal/disp32
 796     # . . discard args
 797     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 798     # . check-next-stream-line-equal(_test-output-stream, "== data", msg)
 799     # . . push args
 800     68/push  "F - test-subx-dquotes-processes-string-literals/3"/imm32
 801     68/push  "== data"/imm32
 802     68/push  _test-output-stream/imm32
 803     # . . call
 804     e8/call  check-next-stream-line-equal/disp32
 805     # . . discard args
 806     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 807     # . check-next-stream-line-equal(_test-output-stream, "_string1: ", msg)
 808     # . . push args
 809     68/push  "F - test-subx-dquotes-processes-string-literals/4"/imm32
 810     68/push  "_string1:"/imm32
 811     68/push  _test-output-stream/imm32
 812     # . . call
 813     e8/call  check-next-stream-line-equal/disp32
 814     # . . discard args
 815     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 816     # . check-next-stream-line-equal(_test-output-stream, "1/imm32 61/a ", msg)
 817     # . . push args
 818     68/push  "F - test-subx-dquotes-processes-string-literals/5"/imm32
 819     68/push  "0x00000001/imm32 61/a "/imm32
 820     68/push  _test-output-stream/imm32
 821     # . . call
 822     e8/call  check-next-stream-line-equal/disp32
 823     # . . discard args
 824     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 825     # . check-next-stream-line-equal(_test-output-stream, "_string2: ", msg)
 826     # . . push args
 827     68/push  "F - test-subx-dquotes-processes-string-literals/6"/imm32
 828     68/push  "_string2:"/imm32
 829     68/push  _test-output-stream/imm32
 830     # . . call
 831     e8/call  check-next-stream-line-equal/disp32
 832     # . . discard args
 833     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 834     # . check-next-stream-line-equal(_test-output-stream, "2/imm32 62/b 63/c ", msg)
 835     # . . push args
 836     68/push  "F - test-subx-dquotes-processes-string-literals/7"/imm32
 837     68/push  "0x00000002/imm32 62/b 63/c "/imm32
 838     68/push  _test-output-stream/imm32
 839     # . . call
 840     e8/call  check-next-stream-line-equal/disp32
 841     # . . discard args
 842     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
 843     # . epilogue
 844     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 845     5d/pop-to-ebp
 846     c3/return
 847 
 848 # generate the data segment contents byte by byte for a given slice
 849 emit-string-literal-data:  # out: (addr stream byte), word: (addr slice)
 850     # pseudocode
 851     #   len = string-length-at-start-of-slice(word->start, word->end)
 852     #   print(out, "#{len}/imm32 ")
 853     #   curr = word->start
 854     #   ++curr  # skip '"'
 855     #   idx = 0
 856     #   while true
 857     #     if (curr >= word->end) break
 858     #     c = *curr
 859     #     if (c == '"') break
 860     #     if (c == '\') {
 861     #       ++curr
 862     #       c = *curr
 863     #       if (c == 'n')
 864     #         c = newline
 865     #     }
 866     #     append-byte-hex(out, c)
 867     #     if c is alphanumeric:
 868     #       write(out, "/")
 869     #       append-byte(out, c)
 870     #     write(out, " ")
 871     #     ++curr
 872     #     ++idx
 873     #     if idx >= 0x40
 874     #       idx = 0
 875     #       write(out, "\n")
 876     #
 877     # . prologue
 878     55/push-ebp
 879     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 880     # . save registers
 881     50/push-eax
 882     51/push-ecx
 883     52/push-edx
 884     53/push-ebx
 885     56/push-esi
 886     # esi = word
 887     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   0xc/disp8       .                 # copy *(ebp+12) to esi
 888     # var idx/ebx: int = 0
 889     31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
 890     # var curr/edx: (addr byte) = word->start
 891     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           2/r32/edx   .               .                 # copy *esi to edx
 892     # var max/esi: (addr byte) = word->end
 893     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           6/r32/esi   4/disp8         .                 # copy *(esi+4) to esi
 894 $emit-string-literal-data:emit-length:
 895     # var len/eax: int = string-length-at-start-of-slice(word->start, word->end)
 896     # . . push args
 897     56/push-esi
 898     52/push-edx
 899     # . . call
 900     e8/call  string-length-at-start-of-slice/disp32
 901     # . . discard args
 902     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 903     # print(out, "#{len}/imm32 ")
 904     # . print-int32(out, len)
 905     # . . push args
 906     50/push-eax
 907     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
 908     # . . call
 909     e8/call  print-int32/disp32
 910     # . . discard args
 911     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 912     # . write(out, "/imm32 ")
 913     # . . push args
 914     68/push  "/imm32 "/imm32
 915     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
 916     # . . call
 917     e8/call  write/disp32
 918     # . . discard args
 919     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 920 $emit-string-literal-data:loop-init:
 921     # ++curr  # skip initial '"'
 922     42/increment-edx
 923     # var c/ecx: byte = 0
 924     31/xor                          3/mod/direct    1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # clear ecx
 925 $emit-string-literal-data:loop:
 926     # if (curr >= max) break
 927     39/compare                      3/mod/direct    2/rm32/edx    .           .             .           6/r32/esi   .               .                 # compare edx with esi
 928     0f 83/jump-if-addr>=  $emit-string-literal-data:end/disp32
 929     # CL = *curr
 930     8a/copy-byte                    0/mod/indirect  2/rm32/edx    .           .             .           1/r32/CL    .               .                 # copy byte at *edx to CL
 931     # if (c == '"') break
 932     81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x22/imm32/dquote # compare ecx
 933     0f 84/jump-if-=  $emit-string-literal-data:end/disp32
 934     # if (c != '\') goto emit
 935     81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x5c/imm32/backslash  # compare ecx
 936     75/jump-if-!=  $emit-string-literal-data:emit/disp8
 937     # ++curr
 938     42/increment-edx
 939     # if (curr >= max) break
 940     39/compare                      3/mod/direct    2/rm32/edx    .           .             .           6/r32/esi   .               .                 # compare edx with esi
 941     0f 83/jump-if-addr>=  $emit-string-literal-data:end/disp32
 942     # c = *curr
 943     8a/copy-byte                    0/mod/indirect  2/rm32/edx    .           .             .           1/r32/CL    .               .                 # copy byte at *edx to CL
 944     # if (c == 'n') c = newline
 945     81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x6e/imm32/n      # compare ecx
 946     75/jump-if-!=  $emit-string-literal-data:emit/disp8
 947     b9/copy-to-ecx  0x0a/imm32/newline
 948 $emit-string-literal-data:emit:
 949     # append-byte-hex(out, CL)
 950     # . . push args
 951     51/push-ecx
 952     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
 953     # . . call
 954     e8/call  append-byte-hex/disp32
 955     # . . discard args
 956     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 957     # if (is-alphanumeric?(*curr)) print(out, "/#{*curr}")
 958     # . var eax: boolean = is-alphanumeric?(CL)
 959     # . . push args
 960     51/push-ecx
 961     # . . call
 962     e8/call  is-alphanumeric?/disp32
 963     # . . discard args
 964     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 965     # . if (eax == false) goto char-done
 966     3d/compare-eax-and  0/imm32/false
 967     74/jump-if-=  $emit-string-literal-data:char-done/disp8
 968     # . write(out, "/")
 969     # . . push args
 970     68/push  Slash/imm32
 971     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
 972     # . . call
 973     e8/call  write/disp32
 974     # . . discard args
 975     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 976     # . append-byte(out, *curr)
 977     # . . push args
 978     51/push-ecx
 979     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
 980     # . . call
 981     e8/call  append-byte/disp32
 982     # . . discard args
 983     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 984 $emit-string-literal-data:char-done:
 985     # write(out, " ")
 986     # . . push args
 987     68/push  Space/imm32
 988     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
 989     # . . call
 990     e8/call  write/disp32
 991     # . . discard args
 992     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
 993     # ++curr
 994     42/increment-edx
 995     # ++ idx
 996     43/increment-ebx
 997     # if (idx < 0x40) continue
 998     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x40/imm32        # compare ebx
 999     7c/jump-if-<  $emit-string-literal-data:next-char/disp8
1000     # idx = 0
1001     31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
1002     # write(out, "\n")
1003     # . . push args
1004     68/push  Newline/imm32
1005     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
1006     # . . call
1007     e8/call  write/disp32
1008     # . . discard args
1009     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1010 $emit-string-literal-data:next-char:
1011     e9/jump $emit-string-literal-data:loop/disp32
1012 $emit-string-literal-data:end:
1013     # . restore registers
1014     5e/pop-to-esi
1015     5b/pop-to-ebx
1016     5a/pop-to-edx
1017     59/pop-to-ecx
1018     58/pop-to-eax
1019     # . epilogue
1020     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1021     5d/pop-to-ebp
1022     c3/return
1023 
1024 is-alphanumeric?:  # c: int -> eax: boolean
1025     # . prologue
1026     55/push-ebp
1027     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1028     # eax = c
1029     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
1030     # if (c < '0') return false
1031     3d/compare-eax-with  0x30/imm32/0
1032     7c/jump-if-<  $is-alphanumeric?:false/disp8
1033     # if (c <= '9') return true
1034     3d/compare-eax-with  0x39/imm32/9
1035     7e/jump-if-<=  $is-alphanumeric?:true/disp8
1036     # if (c < 'A') return false
1037     3d/compare-eax-with  0x41/imm32/A
1038     7c/jump-if-<  $is-alphanumeric?:false/disp8
1039     # if (c <= 'Z') return true
1040     3d/compare-eax-with  0x5a/imm32/Z
1041     7e/jump-if-<=  $is-alphanumeric?:true/disp8
1042     # if (c < 'a') return false
1043     3d/compare-eax-with  0x61/imm32/a
1044     7c/jump-if-<  $is-alphanumeric?:false/disp8
1045     # if (c <= 'z') return true
1046     3d/compare-eax-with  0x7a/imm32/z
1047     7e/jump-if-<=  $is-alphanumeric?:true/disp8
1048     # return false
1049 $is-alphanumeric?:false:
1050     b8/copy-to-eax  0/imm32/false
1051     eb/jump  $is-alphanumeric?:end/disp8
1052 $is-alphanumeric?:true:
1053     b8/copy-to-eax  1/imm32/true
1054 $is-alphanumeric?:end:
1055     # . epilogue
1056     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1057     5d/pop-to-ebp
1058     c3/return
1059 
1060 test-emit-string-literal-data:
1061     # . prologue
1062     55/push-ebp
1063     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1064     # setup
1065     # . clear-stream(_test-output-stream)
1066     # . . push args
1067     68/push  _test-output-stream/imm32
1068     # . . call
1069     e8/call  clear-stream/disp32
1070     # . . discard args
1071     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1072     # var slice/ecx = '"abc"/d'
1073     68/push  _test-slice-abc-limit/imm32
1074     68/push  _test-slice-abc/imm32
1075     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1076     # emit-string-literal-data(_test-output-stream, slice)
1077     # . . push args
1078     51/push-ecx
1079     68/push  _test-output-stream/imm32
1080     # . . call
1081     e8/call  emit-string-literal-data/disp32
1082     # . . discard args
1083     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1084 +-- 26 lines: #?     # dump output ---------------------------------------------------------------------------------------------------------------------------
1110     # . check-stream-equal(_test-output-stream, "3/imm32 61/a 62/b 63/c ", msg)
1111     # . . push args
1112     68/push  "F - test-emit-string-literal-data"/imm32
1113     68/push  "0x00000003/imm32 61/a 62/b 63/c "/imm32
1114     68/push  _test-output-stream/imm32
1115     # . . call
1116     e8/call  check-stream-equal/disp32
1117     # . . discard args
1118     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1119     # . epilogue
1120     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1121     5d/pop-to-ebp
1122     c3/return
1123 
1124 test-emit-string-literal-data-empty:
1125     # . prologue
1126     55/push-ebp
1127     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1128     # setup
1129     # . clear-stream(_test-output-stream)
1130     # . . push args
1131     68/push  _test-output-stream/imm32
1132     # . . call
1133     e8/call  clear-stream/disp32
1134     # . . discard args
1135     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1136     # var slice/ecx = '""'
1137     68/push  0/imm32/end
1138     68/push  0/imm32/start
1139     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1140     # emit-string-literal-data(_test-output-stream, slice)
1141     # . . push args
1142     51/push-ecx
1143     68/push  _test-output-stream/imm32
1144     # . . call
1145     e8/call  emit-string-literal-data/disp32
1146     # . . discard args
1147     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1148 +-- 26 lines: #?     # dump output ---------------------------------------------------------------------------------------------------------------------------
1174     # . check-stream-equal(_test-output-stream, "0/imm32 ", msg)
1175     # . . push args
1176     68/push  "F - test-emit-string-literal-data-empty"/imm32
1177     68/push  "0x00000000/imm32 "/imm32
1178     68/push  _test-output-stream/imm32
1179     # . . call
1180     e8/call  check-stream-equal/disp32
1181     # . . discard args
1182     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1183     # . epilogue
1184     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1185     5d/pop-to-ebp
1186     c3/return
1187 
1188 # just to keep things simple
1189 test-emit-string-literal-data-no-metadata-for-non-alphanumerics:
1190     # . prologue
1191     55/push-ebp
1192     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1193     # setup
1194     # . clear-stream(_test-output-stream)
1195     # . . push args
1196     68/push  _test-output-stream/imm32
1197     # . . call
1198     e8/call  clear-stream/disp32
1199     # . . discard args
1200     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1201     # var slice/ecx = '"a b"'
1202     68/push  _test-slice-a-space-b-limit/imm32
1203     68/push  _test-slice-a-space-b/imm32
1204     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1205     # emit-string-literal-data(_test-output-stream, slice)
1206     # . . push args
1207     51/push-ecx
1208     68/push  _test-output-stream/imm32
1209     # . . call
1210     e8/call  emit-string-literal-data/disp32
1211     # . . discard args
1212     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1213 +-- 26 lines: #?     # dump output ---------------------------------------------------------------------------------------------------------------------------
1239     # . check-stream-equal(_test-output-stream, "3/imm32 61/a 20 62/b ", msg)  # ideally we'd like to say '20/space' but that requires managing names for codepoints
1240     # . . push args
1241     68/push  "F - test-emit-string-literal-data-no-metadata-for-non-alphanumerics"/imm32
1242     68/push  "0x00000003/imm32 61/a 20 62/b "/imm32
1243     68/push  _test-output-stream/imm32
1244     # . . call
1245     e8/call  check-stream-equal/disp32
1246     # . . discard args
1247     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1248     # . epilogue
1249     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1250     5d/pop-to-ebp
1251     c3/return
1252 
1253 test-emit-string-literal-data-handles-escape-sequences:
1254     # . prologue
1255     55/push-ebp
1256     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1257     # setup
1258     # . clear-stream(_test-output-stream)
1259     # . . push args
1260     68/push  _test-output-stream/imm32
1261     # . . call
1262     e8/call  clear-stream/disp32
1263     # . . discard args
1264     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1265     # var slice/ecx = '"a\"b"'
1266     68/push  _test-slice-a-dquote-b-limit/imm32
1267     68/push  _test-slice-a-dquote-b/imm32
1268     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1269     # emit-string-literal-data(_test-output-stream, slice)
1270     # . . push args
1271     51/push-ecx
1272     68/push  _test-output-stream/imm32
1273     # . . call
1274     e8/call  emit-string-literal-data/disp32
1275     # . . discard args
1276     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1277 +-- 26 lines: #?     # dump output ---------------------------------------------------------------------------------------------------------------------------
1303     # . check-stream-equal(_test-output-stream, "3/imm32 61/a 22 62/b ", msg)
1304     # . . push args
1305     68/push  "F - test-emit-string-literal-data-handles-escape-sequences"/imm32
1306     68/push  "0x00000003/imm32 61/a 22 62/b "/imm32
1307     68/push  _test-output-stream/imm32
1308     # . . call
1309     e8/call  check-stream-equal/disp32
1310     # . . discard args
1311     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1312     # . epilogue
1313     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1314     5d/pop-to-ebp
1315     c3/return
1316 
1317 test-emit-string-literal-data-handles-newline-escape:
1318     # . prologue
1319     55/push-ebp
1320     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1321     # setup
1322     # . clear-stream(_test-output-stream)
1323     # . . push args
1324     68/push  _test-output-stream/imm32
1325     # . . call
1326     e8/call  clear-stream/disp32
1327     # . . discard args
1328     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1329     # var slice/ecx = '"a\nb"'
1330     68/push  _test-slice-a-newline-b-limit/imm32
1331     68/push  _test-slice-a-newline-b/imm32
1332     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1333     # emit-string-literal-data(_test-output-stream, slice)
1334     # . . push args
1335     51/push-ecx
1336     68/push  _test-output-stream/imm32
1337     # . . call
1338     e8/call  emit-string-literal-data/disp32
1339     # . . discard args
1340     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1341 +-- 26 lines: #?     # dump output ---------------------------------------------------------------------------------------------------------------------------
1367     # . check-stream-equal(_test-output-stream, "3/imm32 61/a 0a 62/b ", msg)
1368     # . . push args
1369     68/push  "F - test-emit-string-literal-data-handles-newline-escape"/imm32
1370     68/push  "0x00000003/imm32 61/a 0a 62/b "/imm32
1371     68/push  _test-output-stream/imm32
1372     # . . call
1373     e8/call  check-stream-equal/disp32
1374     # . . discard args
1375     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1376     # . epilogue
1377     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1378     5d/pop-to-ebp
1379     c3/return
1380 
1381 # emit everything from a word except the initial datum
1382 emit-metadata:  # out: (addr buffered-file), word: (addr slice)
1383     # pseudocode
1384     #   var slice: slice = {0, word->end}
1385     #   curr = word->start
1386     #   if *curr == '"'
1387     #     curr = skip-string-in-slice(curr, word->end)
1388     #   else
1389     #     while true
1390     #       if curr == word->end
1391     #         return
1392     #       if *curr == '/'
1393     #         break
1394     #       ++curr
1395     #   slice->start = curr
1396     #   write-slice-buffered(out, slice)
1397     #
1398     # . prologue
1399     55/push-ebp
1400     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1401     # . save registers
1402     50/push-eax
1403     51/push-ecx
1404     52/push-edx
1405     53/push-ebx
1406     56/push-esi
1407     # esi = word
1408     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   0xc/disp8       .                 # copy *(ebp+12) to esi
1409     # var curr/ecx: (addr byte) = word->start
1410     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           1/r32/ecx   .               .                 # copy *esi to ecx
1411     # var end/edx: (addr byte) = word->end
1412     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           2/r32/edx   4/disp8         .                 # copy *(esi+4) to edx
1413     # var slice/ebx: slice = {0, end}
1414     52/push-edx
1415     68/push  0/imm32
1416     89/copy                         3/mod/direct    3/rm32/ebx    .           .             .           4/r32/esp   .               .                 # copy esp to ebx
1417     # eax = 0
1418     b8/copy-to-eax  0/imm32
1419 $emit-metadata:check-for-string-literal:
1420     # -  if (*curr == '"') curr = skip-string-in-slice(curr, end)
1421     8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
1422     3d/compare-eax-and  0x22/imm32/dquote
1423     75/jump-if-!=  $emit-metadata:skip-datum-loop/disp8
1424 $emit-metadata:skip-string-literal:
1425     # . eax = skip-string-in-slice(curr, end)
1426     # . . push args
1427     52/push-edx
1428     51/push-ecx
1429     # . . call
1430     e8/call  skip-string-in-slice/disp32
1431     # . . discard args
1432     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1433     # . curr = eax
1434     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy eax to ecx
1435     eb/jump  $emit-metadata:emit/disp8
1436 $emit-metadata:skip-datum-loop:
1437     # - otherwise scan for '/'
1438     # if (curr == end) return
1439     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx and edx
1440     74/jump-if-=  $emit-metadata:end/disp8
1441     # if (*curr == '/') break
1442     8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
1443     3d/compare-eax-and  0x2f/imm32/slash
1444     74/jump-if-=  $emit-metadata:emit/disp8
1445     # ++curr
1446     41/increment-ecx
1447     eb/jump  $emit-metadata:skip-datum-loop/disp8
1448 $emit-metadata:emit:
1449     # slice->start = ecx
1450     89/copy                         0/mod/indirect  3/rm32/ebx    .           .             .           1/r32/ecx   .               .                 # copy ecx to *ebx
1451     # write-slice-buffered(out, slice)
1452     # . . push args
1453     53/push-ebx
1454     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
1455     # . . call
1456     e8/call  write-slice-buffered/disp32
1457     # . . discard args
1458     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           8/imm32      .                    # add to esp
1459 $emit-metadata:end:
1460     # . reclaim locals
1461     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           8/imm32      .                    # add to esp
1462     # . restore registers
1463     5e/pop-to-esi
1464     5b/pop-to-ebx
1465     5a/pop-to-edx
1466     59/pop-to-ecx
1467     58/pop-to-eax
1468     # . epilogue
1469     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1470     5d/pop-to-ebp
1471     c3/return
1472 
1473 test-emit-metadata:
1474     # . prologue
1475     55/push-ebp
1476     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1477     # setup
1478     # . clear-stream(_test-output-stream)
1479     # . . push args
1480     68/push  _test-output-stream/imm32
1481     # . . call
1482     e8/call  clear-stream/disp32
1483     # . . discard args
1484     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1485     # . clear-stream($_test-output-buffered-file->buffer)
1486     # . . push args
1487     68/push  $_test-output-buffered-file->buffer/imm32
1488     # . . call
1489     e8/call  clear-stream/disp32
1490     # . . discard args
1491     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1492     # (eax..ecx) = "abc/def"
1493     b8/copy-to-eax  "abc/def"/imm32
1494     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
1495     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
1496     05/add-to-eax  4/imm32
1497     # var slice/ecx = {eax, ecx}
1498     51/push-ecx
1499     50/push-eax
1500     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1501     # emit-metadata(_test-output-buffered-file, slice)
1502     # . . push args
1503     51/push-ecx
1504     68/push  _test-output-buffered-file/imm32
1505     # . . call
1506     e8/call  emit-metadata/disp32
1507     # . . discard args
1508     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1509     # flush(_test-output-buffered-file)
1510     # . . push args
1511     68/push  _test-output-buffered-file/imm32
1512     # . . call
1513     e8/call  flush/disp32
1514     # . . discard args
1515     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1516     # check-stream-equal(_test-output-stream, "/def", msg)  # important that there's no leading space
1517     # . . push args
1518     68/push  "F - test-emit-metadata"/imm32
1519     68/push  "/def"/imm32
1520     68/push  _test-output-stream/imm32
1521     # . . call
1522     e8/call  check-stream-equal/disp32
1523     # . . discard args
1524     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1525     # . epilogue
1526     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1527     5d/pop-to-ebp
1528     c3/return
1529 
1530 test-emit-metadata-none:
1531     # . prologue
1532     55/push-ebp
1533     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1534     # setup
1535     # . clear-stream(_test-output-stream)
1536     # . . push args
1537     68/push  _test-output-stream/imm32
1538     # . . call
1539     e8/call  clear-stream/disp32
1540     # . . discard args
1541     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1542     # . clear-stream($_test-output-buffered-file->buffer)
1543     # . . push args
1544     68/push  $_test-output-buffered-file->buffer/imm32
1545     # . . call
1546     e8/call  clear-stream/disp32
1547     # . . discard args
1548     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1549     # (eax..ecx) = "abc"
1550     b8/copy-to-eax  "abc"/imm32
1551     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
1552     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
1553     05/add-to-eax  4/imm32
1554     # var slice/ecx = {eax, ecx}
1555     51/push-ecx
1556     50/push-eax
1557     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1558     # emit-metadata(_test-output-buffered-file, slice)
1559     # . . push args
1560     51/push-ecx
1561     68/push  _test-output-buffered-file/imm32
1562     # . . call
1563     e8/call  emit-metadata/disp32
1564     # . . discard args
1565     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1566     # flush(_test-output-buffered-file)
1567     # . . push args
1568     68/push  _test-output-buffered-file/imm32
1569     # . . call
1570     e8/call  flush/disp32
1571     # . . discard args
1572     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1573     # check-stream-equal(_test-output-stream, "", msg)
1574     # . . push args
1575     68/push  "F - test-emit-metadata-none"/imm32
1576     68/push  ""/imm32
1577     68/push  _test-output-stream/imm32
1578     # . . call
1579     e8/call  check-stream-equal/disp32
1580     # . . discard args
1581     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1582     # . epilogue
1583     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1584     5d/pop-to-ebp
1585     c3/return
1586 
1587 test-emit-metadata-multiple:
1588     # . prologue
1589     55/push-ebp
1590     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1591     # setup
1592     # . clear-stream(_test-output-stream)
1593     # . . push args
1594     68/push  _test-output-stream/imm32
1595     # . . call
1596     e8/call  clear-stream/disp32
1597     # . . discard args
1598     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1599     # . clear-stream($_test-output-buffered-file->buffer)
1600     # . . push args
1601     68/push  $_test-output-buffered-file->buffer/imm32
1602     # . . call
1603     e8/call  clear-stream/disp32
1604     # . . discard args
1605     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1606     # (eax..ecx) = "abc/def/ghi"
1607     b8/copy-to-eax  "abc/def/ghi"/imm32
1608     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
1609     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
1610     05/add-to-eax  4/imm32
1611     # var slice/ecx = {eax, ecx}
1612     51/push-ecx
1613     50/push-eax
1614     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1615     # emit-metadata(_test-output-buffered-file, slice)
1616     # . . push args
1617     51/push-ecx
1618     68/push  _test-output-buffered-file/imm32
1619     # . . call
1620     e8/call  emit-metadata/disp32
1621     # . . discard args
1622     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1623     # flush(_test-output-buffered-file)
1624     # . . push args
1625     68/push  _test-output-buffered-file/imm32
1626     # . . call
1627     e8/call  flush/disp32
1628     # . . discard args
1629     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1630     # check-stream-equal(_test-output-stream, "/def/ghi", msg)  # important that there's no leading space
1631     # . . push args
1632     68/push  "F - test-emit-metadata-multiple"/imm32
1633     68/push  "/def/ghi"/imm32
1634     68/push  _test-output-stream/imm32
1635     # . . call
1636     e8/call  check-stream-equal/disp32
1637     # . . discard args
1638     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1639     # . epilogue
1640     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1641     5d/pop-to-ebp
1642     c3/return
1643 
1644 test-emit-metadata-when-no-datum:
1645     # . prologue
1646     55/push-ebp
1647     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1648     # setup
1649     # . clear-stream(_test-output-stream)
1650     # . . push args
1651     68/push  _test-output-stream/imm32
1652     # . . call
1653     e8/call  clear-stream/disp32
1654     # . . discard args
1655     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1656     # . clear-stream($_test-output-buffered-file->buffer)
1657     # . . push args
1658     68/push  $_test-output-buffered-file->buffer/imm32
1659     # . . call
1660     e8/call  clear-stream/disp32
1661     # . . discard args
1662     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1663     # var slice/ecx = "/abc"
1664     b8/copy-to-eax  "/abc"/imm32
1665     # . push end/ecx
1666     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
1667     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
1668     51/push-ecx
1669     # . push curr/eax
1670     05/add-to-eax  4/imm32
1671     50/push-eax
1672     # . save stack pointer
1673     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1674     # emit-metadata(_test-output-buffered-file, slice)
1675     # . . push args
1676     51/push-ecx
1677     68/push  _test-output-buffered-file/imm32
1678     # . . call
1679     e8/call  emit-metadata/disp32
1680     # . . discard args
1681     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1682     # flush(_test-output-buffered-file)
1683     # . . push args
1684     68/push  _test-output-buffered-file/imm32
1685     # . . call
1686     e8/call  flush/disp32
1687     # . . discard args
1688     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1689     # check-stream-equal(_test-output-stream, "/abc", msg)  # nothing skipped
1690     # . . push args
1691     68/push  "F - test-emit-metadata-when-no-datum"/imm32
1692     68/push  "/abc"/imm32
1693     68/push  _test-output-stream/imm32
1694     # . . call
1695     e8/call  check-stream-equal/disp32
1696     # . . discard args
1697     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1698     # . epilogue
1699     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1700     5d/pop-to-ebp
1701     c3/return
1702 
1703 test-emit-metadata-in-string-literal:
1704     # . prologue
1705     55/push-ebp
1706     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1707     # setup
1708     # . clear-stream(_test-output-stream)
1709     # . . push args
1710     68/push  _test-output-stream/imm32
1711     # . . call
1712     e8/call  clear-stream/disp32
1713     # . . discard args
1714     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1715     # . clear-stream($_test-output-buffered-file->buffer)
1716     # . . push args
1717     68/push  $_test-output-buffered-file->buffer/imm32
1718     # . . call
1719     e8/call  clear-stream/disp32
1720     # . . discard args
1721     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1722     # var slice/ecx = "\"abc/def\"/ghi"
1723     68/push  _test-slice-literal-string-with-limit/imm32
1724     68/push  _test-slice-literal-string/imm32/start
1725     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
1726     # emit-metadata(_test-output-buffered-file, slice)
1727     # . . push args
1728     51/push-ecx
1729     68/push  _test-output-buffered-file/imm32
1730     # . . call
1731     e8/call  emit-metadata/disp32
1732     # . . discard args
1733     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1734     # flush(_test-output-buffered-file)
1735     # . . push args
1736     68/push  _test-output-buffered-file/imm32
1737     # . . call
1738     e8/call  flush/disp32
1739     # . . discard args
1740     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
1741 +-- 26 lines: #?     # dump output ---------------------------------------------------------------------------------------------------------------------------
1767     # check-stream-equal(_test-output-stream, "/ghi", msg)  # important that there's no leading space
1768     # . . push args
1769     68/push  "F - test-emit-metadata-in-string-literal"/imm32
1770     68/push  "/ghi"/imm32
1771     68/push  _test-output-stream/imm32
1772     # . . call
1773     e8/call  check-stream-equal/disp32
1774     # . . discard args
1775     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1776     # . epilogue
1777     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1778     5d/pop-to-ebp
1779     c3/return
1780 
1781 string-length-at-start-of-slice:  # curr: (addr byte), end: (addr byte) -> length/eax
1782     # . prologue
1783     55/push-ebp
1784     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1785     # . save registers
1786     51/push-ecx
1787     52/push-edx
1788     53/push-ebx
1789     # ecx = curr
1790     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
1791     # edx = end
1792     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         2/r32/edx   0xc/disp8         .               # copy *(ebp+12) to edx
1793     # var length/eax: int = 0
1794     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
1795     # var c/ebx: byte = 0
1796     31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
1797     # skip initial dquote
1798     41/increment-ecx
1799 $string-length-at-start-of-slice:loop:
1800     # if (curr >= end) return length
1801     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
1802     73/jump-if-addr>=  $string-length-at-start-of-slice:end/disp8
1803     # c = *curr
1804     8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           3/r32/BL    .               .                 # copy byte at *ecx to BL
1805 $string-length-at-start-of-slice:dquote:
1806     # if (c == '"') break
1807     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x22/imm32/dquote # compare ebx
1808     74/jump-if-=  $string-length-at-start-of-slice:end/disp8
1809 $string-length-at-start-of-slice:check-for-escape:
1810     # if (c == '\') escape next char
1811     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x5c/imm32/backslash # compare ebx
1812     75/jump-if-!=  $string-length-at-start-of-slice:continue/disp8
1813 $string-length-at-start-of-slice:escape:
1814     # increment curr but not result
1815     41/increment-ecx
1816 $string-length-at-start-of-slice:continue:
1817     # ++result
1818     40/increment-eax
1819     # ++curr
1820     41/increment-ecx
1821     eb/jump  $string-length-at-start-of-slice:loop/disp8
1822 $string-length-at-start-of-slice:end:
1823     # . restore registers
1824     5b/pop-to-ebx
1825     5a/pop-to-edx
1826     59/pop-to-ecx
1827     # . epilogue
1828     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1829     5d/pop-to-ebp
1830     c3/return
1831 
1832 test-string-length-at-start-of-slice:
1833     # . prologue
1834     55/push-ebp
1835     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1836     # setup: (eax..ecx) = "\"abc\" def"
1837     b8/copy-to-eax  "\"abc\" def"/imm32
1838     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
1839     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
1840     05/add-to-eax  4/imm32
1841     # eax = string-length-at-start-of-slice(eax, ecx)
1842     # . . push args
1843     51/push-ecx
1844     50/push-eax
1845     # . . call
1846     e8/call  string-length-at-start-of-slice/disp32
1847     # . . discard args
1848     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1849     # check-ints-equal(eax, 3, msg)
1850     # . . push args
1851     68/push  "F - test-string-length-at-start-of-slice"/imm32
1852     68/push  3/imm32
1853     50/push-eax
1854     # . . call
1855     e8/call  check-ints-equal/disp32
1856     # . . discard args
1857     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1858     # . epilogue
1859     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1860     5d/pop-to-ebp
1861     c3/return
1862 
1863 test-string-length-at-start-of-slice-escaped:
1864     # . prologue
1865     55/push-ebp
1866     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
1867     # setup: (eax..ecx) = "\"ab\\c\" def"
1868     b8/copy-to-eax  "\"ab\\c\" def"/imm32
1869     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
1870     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
1871     05/add-to-eax  4/imm32
1872     # eax = string-length-at-start-of-slice(eax, ecx)
1873     # . . push args
1874     51/push-ecx
1875     50/push-eax
1876     # . . call
1877     e8/call  string-length-at-start-of-slice/disp32
1878     # . . discard args
1879     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
1880     # check-ints-equal(eax, 3, msg)
1881     # . . push args
1882     68/push  "F - test-string-length-at-start-of-slice-escaped"/imm32
1883     68/push  3/imm32
1884     50/push-eax
1885     # . . call
1886     e8/call  check-ints-equal/disp32
1887     # . . discard args
1888     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
1889     # . epilogue
1890     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
1891     5d/pop-to-ebp
1892     c3/return
1893 
1894 == data
1895 
1896 Next-string-literal:  # tracks the next auto-generated variable name
1897   1/imm32
1898 
1899 _test-slice-abc:
1900   22/dquote 61/a 62/b 63/c 22/dquote  # "abc"
1901   2f/slash 64/d
1902 _test-slice-abc-limit:
1903 
1904 _test-slice-a-space-b:
1905   22/dquote 61/a 20/space 62/b 22/dquote  # "a b"
1906 _test-slice-a-space-b-limit:
1907 
1908 _test-slice-a-dquote-b:
1909   22/dquote 61/a 5c/backslash 22/dquote 62/b 22/dquote  # "a\"b"
1910 _test-slice-a-dquote-b-limit:
1911 
1912 _test-slice-a-newline-b:
1913   22/dquote 61/a 5c/backslash 6e/n 62/b 22/dquote  # "a\nb"
1914 _test-slice-a-newline-b-limit:
1915 
1916 # "abc/def"/ghi
1917 _test-slice-literal-string:
1918   22/dquote
1919   61/a 62/b 63/c  # abc
1920   2f/slash 64/d 65/e 66/f  # /def
1921   22/dquote
1922   2f/slash 67/g 68/h 69/i  # /ghi
1923 _test-slice-literal-string-with-limit:
1924 
1925 # . . vim:nowrap:textwidth=0