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