https://github.com/akkartik/mu/blob/master/subx/apps/crenshaw2-1b.subx
  1 # Port of https://github.com/akkartik/crenshaw/blob/master/tutor2.1.pas
  2 # which corresponds to the section "single digits" in https://compilers.iecc.com/crenshaw/tutor2.txt
  3 # except that we support hex numbers of multiple digits.
  4 #
  5 # To run (from the subx/ directory):
  6 #   $ ./subx translate *.subx apps/crenshaw2-1b.subx -o apps/crenshaw2-1b
  7 #   $ echo '1a'  |./subx run apps/crenshaw2-1b
  8 # Expected output:
  9 #   # syscall(exit, 1a)
 10 #   bb/copy-to-EBX  3/imm32
 11 #   b8/copy-to-EAX  1/imm32/exit
 12 #   cd/syscall  0x80/imm8
 13 #
 14 # To run the generated output:
 15 #   $ echo '1a'  |./subx run apps/crenshaw2-1b > z1.subx
 16 #   $ ./subx translate z1.subx -o z1
 17 #   $ ./subx run z1
 18 #   $ echo $?
 19 #   26  # 0x1a in decimal
 20 #
 21 # Stdin must contain just a single hex digit. Other input will print an error:
 22 #   $ echo 'xyz'  |./subx run apps/crenshaw2-1b
 23 #   Error: integer expected
 24 #
 25 # Names in this file sometimes follow Crenshaw's original rather than my usual
 26 # naming conventions.
 27 
 28 == code
 29 #   instruction                     effective address                                                   register    displacement    immediate
 30 # . op          subop               mod             rm32          base        index         scale       r32
 31 # . 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
 32 
 33 Entry:  # run tests if necessary, call 'compile' if not
 34 
 35 #?     # for debugging: run a single test; don't bother setting status code
 36 #?     e8/call test-get-num-reads-single-digit/disp32
 37 #?     eb/jump  $main:end/disp8
 38 
 39     # . prolog
 40     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 41     # - if argc > 1 and argv[1] == "test", then return run_tests()
 42     # . argc > 1
 43     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0/disp8         1/imm32           # compare *EBP
 44     7e/jump-if-lesser-or-equal  $run-main/disp8
 45     # . argv[1] == "test"
 46     # . . push args
 47     68/push  "test"/imm32
 48     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
 49     # . . call
 50     e8/call  kernel-string-equal?/disp32
 51     # . . discard args
 52     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
 53     # . check result
 54     3d/compare-EAX-and  1/imm32
 55     75/jump-if-not-equal  $run-main/disp8
 56     # . run-tests()
 57     e8/call  run-tests/disp32
 58     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           3/r32/EBX   Num-test-failures/disp32          # copy *Num-test-failures to EBX
 59     eb/jump  $main:end/disp8
 60 $run-main:
 61     # - otherwise read a program from stdin and emit its translation to stdout
 62     # var ed/EAX : exit-descriptor
 63     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
 64     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
 65     # configure ed to really exit()
 66     # . ed->target = 0
 67     c7          0/subop/copy        0/mod/direct    0/rm32/EAX    .           .             .           .           .               0/imm32           # copy to *EAX
 68     # return compile(Stdin, 1/stdout, 2/stderr, ed)
 69     # . . push args
 70     50/push-EAX/ed
 71     68/push  2/imm32/stderr
 72     68/push  1/imm32/stdout
 73     68/push  Stdin/imm32
 74     # . . call
 75     e8/call  compile/disp32
 76     # . . discard args
 77     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
 78     # . syscall(exit, 0)
 79     bb/copy-to-EBX  0/imm32
 80 $main:end:
 81     b8/copy-to-EAX  1/imm32/exit
 82     cd/syscall  0x80/imm8
 83 
 84 # the main entry point
 85 compile:  # in : (address buffered-file), out : fd or (address stream), err : fd or (address stream), ed : (address exit-descriptor) -> <void>
 86     # . prolog
 87     55/push-EBP
 88     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
 89     # . save registers
 90     50/push-EAX
 91     51/push-ECX
 92     # prime the pump
 93     # . Look = get-char(in)
 94     # . . push args
 95     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8      .                    # push *(EBP+8)
 96     # . . call
 97     e8/call  get-char/disp32
 98     # . . discard args
 99     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
100     # var num/ECX : (address stream) on the stack
101     # Numbers can be 32 bits or 8 hex bytes long. One of them will be in 'Look', so we need space for 7 bytes.
102     # Sizing the stream just right buys us overflow-handling for free inside 'get-num'.
103     # Add 12 bytes for 'read', 'write' and 'length' fields, for a total of 19 bytes, or 0x13 in hex.
104     # The stack pointer is no longer aligned, so dump_stack() can be misleading past this point.
105     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x13/imm32        # subtract from ESP
106     89/copy                         3/mod/direct    1/rm32/ECX    .           .             .           4/r32/ESP   .               .                 # copy ESP to ECX
107     # initialize the stream
108     # . num->length = 7
109     c7          0/subop/copy        1/mod/*+disp8   1/rm32/ECX    .           .             .           .           8/disp8         7/imm32           # copy to *(ECX+8)
110     # . clear-stream(num)
111     # . . push args
112     51/push-ECX
113     # . . call
114     e8/call  clear-stream/disp32
115     # . . discard args
116     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
117     # read a digit from 'in' into 'num'
118     # . get-num(in, num, err, ed)
119     # . . push args
120     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
121     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
122     51/push-ECX/num
123     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8      .                    # push *(EBP+8)
124     # . . call
125     e8/call  get-num/disp32
126     # . . discard args
127     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
128     # render 'num' into the following template on 'out':
129     #   bb/copy-to-EBX  _num_
130     #   b8/copy-to-EAX  1/imm32/exit
131     #   cd/syscall  0x80/imm8
132     #
133     # . write(out, "bb/copy-to-EBX  ")
134     # . . push args
135     68/push  "bb/copy-to-EBX  "/imm32
136     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
137     # . . call
138     e8/call  write/disp32
139     # . . discard args
140     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
141     # . write-stream(out, num)
142     # . . push args
143     51/push-ECX/num
144     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
145     # . . call
146     e8/call  write-stream/disp32
147     # . . discard args
148     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
149     # . write(out, Newline)
150     # . . push args
151     68/push  Newline/imm32
152     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
153     # . . call
154     e8/call  write/disp32
155     # . . discard args
156     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
157     # . write(out, "b8/copy-to-EAX  1/imm32/exit\n")
158     # . . push args
159     68/push  "b8/copy-to-EAX  1/imm32/exit\n"/imm32
160     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
161     # . . call
162     e8/call  write/disp32
163     # . . discard args
164     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
165     # . write(out, "cd/syscall  0x80/imm8\n")
166     # . . push args
167     68/push  "cd/syscall  0x80/imm8\n"/imm32
168     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
169     # . . call
170     e8/call  write/disp32
171     # . . discard args
172     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
173 $compile:end:
174     # . restore registers
175     59/pop-to-ECX
176     58/pop-to-EAX
177     # . epilog
178     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
179     5d/pop-to-EBP
180     c3/return
181 
182 # Read a sequence of digits into 'out'. Abort if there are none, or if there is
183 # no space in 'out'.
184 # Input comes from the global variable 'Look' (first byte) and the argument
185 # 'in' (rest). We leave the next byte from 'in' into 'Look' on exit.
186 get-num:  # in : (address buffered-file), out : (address stream), err : fd or (address stream), ed : (address exit-descriptor) -> <void>
187     # pseudocode:
188     #   if (!is-digit?(Look)) expected(ed, err, "integer")
189     #   do
190     #     if out->write >= out->length
191     #       write(err, "Error: too many digits in number\n")
192     #       stop(ed, 1)
193     #     out->data[out->write] = LSB(Look)
194     #     ++out->write
195     #     Look = get-char(in)
196     #   while is-digit?(Look)
197     # This is complicated because I don't want to hard-code the error strategy in
198     # a general helper like write-byte. Maybe I should just create a local helper.
199     #
200     # within the loop we'll try to keep things in registers:
201     #   in: ESI
202     #   out: EDI
203     #   out->write: ECX (cached copy; need to keep in sync)
204     #   out->length: EDX
205     #   temporaries: EAX, EBX
206     # We can't allocate Look to a register because it gets written implicitly in
207     # get-char in each iteration of the loop. (Thereby demonstrating that it's
208     # not the right interface for us. But we'll keep it just to follow Crenshaw.)
209     #
210     # . prolog
211     55/push-EBP
212     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
213     # - if (is-digit?(Look)) expected(ed, err, "integer")
214     # . EAX = is-digit?(Look)
215     # . . push args
216     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Look/disp32     .                 # push *Look
217     # . . call
218     e8/call  is-digit?/disp32
219     # . . discard args
220     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
221     # . if (EAX == 0)
222     3d/compare-EAX-and  0/imm32
223     75/jump-if-not-equal  $get-num:main/disp8
224     # . expected(ed, err, "integer")
225     # . . push args
226     68/push  "integer"/imm32
227     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
228     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
229     # . . call
230     e8/call  expected/disp32  # never returns
231     # . . discard args
232     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
233 $get-num:main:
234     # - otherwise read a digit
235     # . save registers
236     50/push-EAX
237     51/push-ECX
238     52/push-EDX
239     53/push-EBX
240     56/push-ESI
241     57/push-EDI
242     # read necessary variables to registers
243     # ESI = in
244     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
245     # EDI = out
246     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
247     # ECX = out->write
248     8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy *EDI to ECX
249     # EDX = out->length
250     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # copy *(EDI+8) to EDX
251 $get-num:loop:
252     # if (out->write >= out->length) error
253     39/compare                      3/mod/direct    2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # compare EDX with ECX
254     7d/jump-if-lesser  $get-num:loop-stage2/disp8
255     # . error(ed, err, msg)  # TODO: show full number
256     # . . push args
257     68/push  "get-num: too many digits in number"/imm32
258     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
259     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
260     # . . call
261     e8/call  error/disp32  # never returns
262     # . . discard args
263     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
264 $get-num:loop-stage2:
265     # out->data[out->write] = LSB(Look)
266     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/EDI  1/index/ECX   .           3/r32/EBX   0xc/disp8       .                 # copy EDI+ECX+12 to EBX
267     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy *Look to EAX
268     88/copy-byte                    0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/AL    .               .                 # copy byte at AL to *EBX
269     # ++out->write
270     41/increment-ECX
271     # Look = get-char(in)
272     # . . push args
273     56/push-ESI
274     # . . call
275     e8/call  get-char/disp32
276     # . . discard args
277     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
278     # if (is-digit?(Look)) loop
279     # . EAX = is-digit?(Look)
280     # . . push args
281     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Look/disp32     .                 # push *Look
282     # . . call
283     e8/call  is-digit?/disp32
284     # . . discard args
285     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
286     # . if (EAX != 0) loop
287     3d/compare-EAX-and  0/imm32
288     0f 85/jump-if-not-equal  $get-num:loop/disp32
289 $get-num:loop-end:
290     # persist necessary variables from registers
291     89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EDI
292 $get-num:end:
293     # . restore registers
294     5f/pop-to-EDI
295     5e/pop-to-ESI
296     5b/pop-to-EBX
297     5a/pop-to-EDX
298     59/pop-to-ECX
299     58/pop-to-EAX
300     # . epilog
301     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
302     5d/pop-to-EBP
303     c3/return
304 
305 test-get-num-reads-single-digit:
306     # - check that get-num returns first character if it's a digit
307     # This test uses exit-descriptors. Use EBP for setting up local variables.
308     55/push-EBP
309     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
310     # clear all streams
311     # . clear-stream(_test-stream)
312     # . . push args
313     68/push  _test-stream/imm32
314     # . . call
315     e8/call  clear-stream/disp32
316     # . . discard args
317     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
318     # . clear-stream(_test-buffered-file+4)
319     # . . push args
320     b8/copy-to-EAX  _test-buffered-file/imm32
321     05/add-to-EAX  4/imm32
322     50/push-EAX
323     # . . call
324     e8/call  clear-stream/disp32
325     # . . discard args
326     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
327     # . clear-stream(_test-output-stream)
328     # . . push args
329     68/push  _test-output-stream/imm32
330     # . . call
331     e8/call  clear-stream/disp32
332     # . . discard args
333     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
334     # . clear-stream(_test-error-stream)
335     # . . push args
336     68/push  _test-error-stream/imm32
337     # . . call
338     e8/call  clear-stream/disp32
339     # . . discard args
340     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
341     # initialize 'in'
342     # . write(_test-stream, "3")
343     # . . push args
344     68/push  "3"/imm32
345     68/push  _test-stream/imm32
346     # . . call
347     e8/call  write/disp32
348     # . . discard args
349     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
350     # initialize exit-descriptor 'ed' for the call to 'get-num' below
351     # . var ed/EAX : exit-descriptor
352     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
353     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
354     # . tailor-exit-descriptor(ed, 16)
355     # . . push args
356     68/push  0x10/imm32/nbytes-of-args-for-get-num
357     50/push-EAX/ed
358     # . . call
359     e8/call  tailor-exit-descriptor/disp32
360     # . . discard args
361     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
362     # prime the pump
363     # . get-char(_test-buffered-file)
364     # . . push args
365     68/push  _test-buffered-file/imm32
366     # . . call
367     e8/call  get-char/disp32
368     # . . discard args
369     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
370     # get-num(in, out, err, ed)
371     # . . push args
372     50/push-EAX/ed
373     68/push  _test-error-stream/imm32
374     68/push  _test-output-stream/imm32
375     68/push  _test-buffered-file/imm32
376     # . . call
377     e8/call  get-num/disp32
378     # registers except ESP may be clobbered at this point
379     # . . discard args
380     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
381     # check-ints-equal(*_test-output-stream->data, '3', msg)
382     # . . push args
383     68/push  "F - test-get-num-reads-single-digit"/imm32
384     68/push  0x33/imm32
385     b8/copy-to-EAX  _test-output-stream/imm32
386     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
387     # . . call
388     e8/call  check-ints-equal/disp32
389     # . . discard args
390     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
391     # . reclaim locals
392     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
393     5d/pop-to-EBP
394     c3/return
395 
396 test-get-num-aborts-on-non-digit-in-Look:
397     # - check that get-num returns first character if it's a digit
398     # This test uses exit-descriptors. Use EBP for setting up local variables.
399     55/push-EBP
400     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
401     # clear all streams
402     # . clear-stream(_test-stream)
403     # . . push args
404     68/push  _test-stream/imm32
405     # . . call
406     e8/call  clear-stream/disp32
407     # . . discard args
408     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
409     # . clear-stream(_test-buffered-file+4)
410     # . . push args
411     b8/copy-to-EAX  _test-buffered-file/imm32
412     05/add-to-EAX  4/imm32
413     50/push-EAX
414     # . . call
415     e8/call  clear-stream/disp32
416     # . . discard args
417     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
418     # . clear-stream(_test-output-stream)
419     # . . push args
420     68/push  _test-output-stream/imm32
421     # . . call
422     e8/call  clear-stream/disp32
423     # . . discard args
424     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
425     # . clear-stream(_test-error-stream)
426     # . . push args
427     68/push  _test-error-stream/imm32
428     # . . call
429     e8/call  clear-stream/disp32
430     # . . discard args
431     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
432     # initialize 'in'
433     # . write(_test-stream, "3")
434     # . . push args
435     68/push  "3"/imm32
436     68/push  _test-stream/imm32
437     # . . call
438     e8/call  write/disp32
439     # . . discard args
440     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
441     # initialize exit-descriptor 'ed' for the call to 'get-num' below
442     # . var ed/EAX : (address exit-descriptor)
443     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
444     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
445     # . tailor-exit-descriptor(ed, 16)
446     # . . push args
447     68/push  0x10/imm32/nbytes-of-args-for-get-num
448     50/push-EAX/ed
449     # . . call
450     e8/call  tailor-exit-descriptor/disp32
451     # . . discard args
452     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
453     # *don't* prime the pump
454     # get-num(in, out, err, ed)
455     # . . push args
456     50/push-EAX/ed
457     68/push  _test-error-stream/imm32
458     68/push  _test-output-stream/imm32
459     68/push  _test-buffered-file/imm32
460     # . . call
461     e8/call  get-num/disp32
462     # registers except ESP may be clobbered at this point
463     # . . discard args
464     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
465     # check that get-num tried to call exit(1)
466     # . check-ints-equal(ed->value, 2, msg)  # i.e. stop was called with value 1
467     # . . push args
468     68/push  "F - test-get-num-aborts-on-non-digit-in-Look"/imm32
469     68/push  2/imm32
470     # . . push ed->value
471     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
472     # . . call
473     e8/call  check-ints-equal/disp32
474     # . . discard args
475     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
476     # . reclaim locals
477     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
478     5d/pop-to-EBP
479     c3/return
480 
481 test-get-num-reads-multiple-digits:
482     # - check that get-num returns all initial digits until it encounters a non-digit
483     # This test uses exit-descriptors. Use EBP for setting up local variables.
484     55/push-EBP
485     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
486     # clear all streams
487     # . clear-stream(_test-stream)
488     # . . push args
489     68/push  _test-stream/imm32
490     # . . call
491     e8/call  clear-stream/disp32
492     # . . discard args
493     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
494     # . clear-stream(_test-buffered-file+4)
495     # . . push args
496     b8/copy-to-EAX  _test-buffered-file/imm32
497     05/add-to-EAX  4/imm32
498     50/push-EAX
499     # . . call
500     e8/call  clear-stream/disp32
501     # . . discard args
502     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
503     # . clear-stream(_test-output-stream)
504     # . . push args
505     68/push  _test-output-stream/imm32
506     # . . call
507     e8/call  clear-stream/disp32
508     # . . discard args
509     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
510     # . clear-stream(_test-error-stream)
511     # . . push args
512     68/push  _test-error-stream/imm32
513     # . . call
514     e8/call  clear-stream/disp32
515     # . . discard args
516     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
517     # initialize 'in'
518     # . write(_test-stream, "3456 x")
519     # . . push args
520     68/push  "3456"/imm32
521     68/push  _test-stream/imm32
522     # . . call
523     e8/call  write/disp32
524     # . . discard args
525     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
526     # initialize exit-descriptor 'ed' for the call to 'get-num' below
527     # . var ed/EAX : (address exit-descriptor)
528     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
529     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
530     # . tailor-exit-descriptor(ed, 16)
531     # . . push args
532     68/push  0x10/imm32/nbytes-of-args-for-get-num
533     50/push-EAX/ed
534     # . . call
535     e8/call  tailor-exit-descriptor/disp32
536     # . . discard args
537     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
538     # prime the pump
539     # . get-char(_test-buffered-file)
540     # . . push args
541     68/push  _test-buffered-file/imm32
542     # . . call
543     e8/call  get-char/disp32
544     # . . discard args
545     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
546     # get-num(in, out, err, ed)
547     # . . push args
548     50/push-EAX/ed
549     68/push  _test-error-stream/imm32
550     68/push  _test-output-stream/imm32
551     68/push  _test-buffered-file/imm32
552     # . . call
553     e8/call  get-num/disp32
554     # registers except ESP may be clobbered at this point
555     # . . discard args
556     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
557     # check-ints-equal(*_test-output-stream->data, '3456', msg)
558     # . . push args
559     68/push  "F - test-get-num-reads-multiple-digits"/imm32
560     68/push  0x36353433/imm32
561     b8/copy-to-EAX  _test-output-stream/imm32
562     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
563     # . . call
564     e8/call  check-ints-equal/disp32
565     # . . discard args
566     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
567     # . reclaim locals
568     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
569     5d/pop-to-EBP
570     c3/return
571 
572 test-get-num-reads-multiple-digits-followed-by-nondigit:
573     # - check that get-num returns all initial digits until it encounters a non-digit
574     # This test uses exit-descriptors. Use EBP for setting up local variables.
575     55/push-EBP
576     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
577     # clear all streams
578     # . clear-stream(_test-stream)
579     # . . push args
580     68/push  _test-stream/imm32
581     # . . call
582     e8/call  clear-stream/disp32
583     # . . discard args
584     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
585     # . clear-stream(_test-buffered-file+4)
586     # . . push args
587     b8/copy-to-EAX  _test-buffered-file/imm32
588     05/add-to-EAX  4/imm32
589     50/push-EAX
590     # . . call
591     e8/call  clear-stream/disp32
592     # . . discard args
593     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
594     # . clear-stream(_test-output-stream)
595     # . . push args
596     68/push  _test-output-stream/imm32
597     # . . call
598     e8/call  clear-stream/disp32
599     # . . discard args
600     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
601     # . clear-stream(_test-error-stream)
602     # . . push args
603     68/push  _test-error-stream/imm32
604     # . . call
605     e8/call  clear-stream/disp32
606     # . . discard args
607     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
608     # initialize 'in'
609     # . write(_test-stream, "3456 x")
610     # . . push args
611     68/push  "3456 x"/imm32
612     68/push  _test-stream/imm32
613     # . . call
614     e8/call  write/disp32
615     # . . discard args
616     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
617     # initialize exit-descriptor 'ed' for the call to 'get-num' below
618     # . var ed/EAX : (address exit-descriptor)
619     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
620     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
621     # . tailor-exit-descriptor(ed, 16)
622     # . . push args
623     68/push  0x10/imm32/nbytes-of-args-for-get-num
624     50/push-EAX/ed
625     # . . call
626     e8/call  tailor-exit-descriptor/disp32
627     # . . discard args
628     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
629     # prime the pump
630     # . get-char(_test-buffered-file)
631     # . . push args
632     68/push  _test-buffered-file/imm32
633     # . . call
634     e8/call  get-char/disp32
635     # . . discard args
636     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
637     # get-num(in, out, err, ed)
638     # . . push args
639     50/push-EAX/ed
640     68/push  _test-error-stream/imm32
641     68/push  _test-output-stream/imm32
642     68/push  _test-buffered-file/imm32
643     # . . call
644     e8/call  get-num/disp32
645     # registers except ESP may be clobbered at this point
646     # . . discard args
647     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
648     # check-ints-equal(*_test-output-stream->data, '3456', msg)
649     # . . push args
650     68/push  "F - test-get-num-reads-multiple-digits-followed-by-nondigit"/imm32
651     68/push  0x36353433/imm32
652     b8/copy-to-EAX  _test-output-stream/imm32
653     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
654     # . . call
655     e8/call  check-ints-equal/disp32
656     # . . discard args
657     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
658     # . reclaim locals
659     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
660     5d/pop-to-EBP
661     c3/return
662 
663 ## helpers
664 
665 # write(f, "Error: "+s+" expected\n") then stop(ed, 1)
666 expected:  # ed : (address exit-descriptor), f : fd or (address stream), s : (address array byte) -> <void>
667     # . prolog
668     55/push-EBP
669     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
670     # write(f, "Error: ")
671     # . . push args
672     68/push  "Error: "/imm32
673     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
674     # . . call
675     e8/call  write/disp32
676     # . . discard args
677     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
678     # write(f, s)
679     # . . push args
680     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
681     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
682     # . . call
683     e8/call  write/disp32
684     # . . discard args
685     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
686     # write(f, " expected\n")
687     # . . push args
688     68/push  " expected\n"/imm32
689     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
690     # . . call
691     e8/call  write/disp32
692     # . . discard args
693     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
694     # stop(ed, 1)
695     # . . push args
696     68/push  1/imm32
697     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
698     # . . call
699     e8/call  stop/disp32
700     # should never get past this point
701 $expected:dead-end:
702     # . epilog
703     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
704     5d/pop-to-EBP
705     c3/return
706 
707 # read a byte from 'f', and save it in 'Look'
708 get-char:  # f : (address buffered-file) -> <void>
709     # . prolog
710     55/push-EBP
711     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
712     # . save registers
713     50/push-EAX
714     # read-byte(f)
715     # . . push args
716     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
717     # . . call
718     e8/call  read-byte/disp32
719     # . . discard args
720     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
721     # save EAX to Look
722     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy EAX to *Look
723 $get-char:end:
724     # . restore registers
725     58/pop-to-EAX
726     # . epilog
727     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
728     5d/pop-to-EBP
729     c3/return
730 
731 is-digit?:  # c : int -> EAX : boolean
732     # . prolog
733     55/push-EBP
734     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
735     # EAX = false
736     b8/copy-to-EAX  0/imm32
737     # if (c < '0') return false
738     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x30/imm32        # compare *(EBP+8)
739     7c/jump-if-lesser  $is-digit?:end/disp8
740     # if (c > '9') return false
741     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x39/imm32        # compare *(EBP+8)
742     7f/jump-if-greater  $is-digit?:end/disp8
743     # otherwise return true
744     b8/copy-to-EAX  1/imm32
745 $is-digit?:end:
746     # . epilog
747     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
748     5d/pop-to-EBP
749     c3/return
750 
751 == data
752 
753 Look:  # (char with some extra padding)
754     0/imm32
755 
756 _test-output-stream:
757     # current write index
758     0/imm32
759     # current read index
760     0/imm32
761     # length
762     8/imm32
763     # data
764     00 00 00 00 00 00 00 00  # 8 bytes
765 
766 _test-error-stream:
767     # current write index
768     0/imm32
769     # current read index
770     0/imm32
771     # length
772     0x40/imm32
773     # data (4 lines x 16 bytes/line)
774     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
775     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
776     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
777     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
778 
779 # . . vim:nowrap:textwidth=0