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-buffered. Maybe I should just create a
199     # local helper.
200     #
201     # within the loop we'll try to keep things in registers:
202     #   in: ESI
203     #   out: EDI
204     #   out->write: ECX (cached copy; need to keep in sync)
205     #   out->length: EDX
206     #   temporaries: EAX, EBX
207     # We can't allocate Look to a register because it gets written implicitly in
208     # get-char in each iteration of the loop. (Thereby demonstrating that it's
209     # not the right interface for us. But we'll keep it just to follow Crenshaw.)
210     #
211     # . prolog
212     55/push-EBP
213     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
214     # - if (is-digit?(Look)) expected(ed, err, "integer")
215     # . EAX = is-digit?(Look)
216     # . . push args
217     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Look/disp32     .                 # push *Look
218     # . . call
219     e8/call  is-digit?/disp32
220     # . . discard args
221     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
222     # . if (EAX == 0)
223     3d/compare-EAX-and  0/imm32
224     75/jump-if-not-equal  $get-num:main/disp8
225     # . expected(ed, err, "integer")
226     # . . push args
227     68/push  "integer"/imm32
228     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
229     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
230     # . . call
231     e8/call  expected/disp32  # never returns
232     # . . discard args
233     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
234 $get-num:main:
235     # - otherwise read a digit
236     # . save registers
237     50/push-EAX
238     51/push-ECX
239     52/push-EDX
240     53/push-EBX
241     56/push-ESI
242     57/push-EDI
243     # read necessary variables to registers
244     # ESI = in
245     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
246     # EDI = out
247     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
248     # ECX = out->write
249     8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy *EDI to ECX
250     # EDX = out->length
251     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # copy *(EDI+8) to EDX
252 $get-num:loop:
253     # if (out->write >= out->length) error
254     39/compare                      3/mod/direct    2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # compare EDX with ECX
255     7d/jump-if-lesser  $get-num:loop-stage2/disp8
256     # . error(ed, err, msg)  # TODO: show full number
257     # . . push args
258     68/push  "get-num: too many digits in number"/imm32
259     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
260     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
261     # . . call
262     e8/call  error/disp32  # never returns
263     # . . discard args
264     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
265 $get-num:loop-stage2:
266     # out->data[out->write] = LSB(Look)
267     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
268     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy *Look to EAX
269     88/copy-byte                    0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/AL    .               .                 # copy byte at AL to *EBX
270     # ++out->write
271     41/increment-ECX
272     # Look = get-char(in)
273     # . . push args
274     56/push-ESI
275     # . . call
276     e8/call  get-char/disp32
277     # . . discard args
278     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
279     # if (is-digit?(Look)) loop
280     # . EAX = is-digit?(Look)
281     # . . push args
282     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Look/disp32     .                 # push *Look
283     # . . call
284     e8/call  is-digit?/disp32
285     # . . discard args
286     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
287     # . if (EAX != 0) loop
288     3d/compare-EAX-and  0/imm32
289     0f 85/jump-if-not-equal  $get-num:loop/disp32
290 $get-num:loop-end:
291     # persist necessary variables from registers
292     89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EDI
293 $get-num:end:
294     # . restore registers
295     5f/pop-to-EDI
296     5e/pop-to-ESI
297     5b/pop-to-EBX
298     5a/pop-to-EDX
299     59/pop-to-ECX
300     58/pop-to-EAX
301     # . epilog
302     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
303     5d/pop-to-EBP
304     c3/return
305 
306 test-get-num-reads-single-digit:
307     # - check that get-num returns first character if it's a digit
308     # This test uses exit-descriptors. Use EBP for setting up local variables.
309     55/push-EBP
310     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
311     # clear all streams
312     # . clear-stream(_test-stream)
313     # . . push args
314     68/push  _test-stream/imm32
315     # . . call
316     e8/call  clear-stream/disp32
317     # . . discard args
318     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
319     # . clear-stream(_test-buffered-file+4)
320     # . . push args
321     b8/copy-to-EAX  _test-buffered-file/imm32
322     05/add-to-EAX  4/imm32
323     50/push-EAX
324     # . . call
325     e8/call  clear-stream/disp32
326     # . . discard args
327     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
328     # . clear-stream(_test-output-stream)
329     # . . push args
330     68/push  _test-output-stream/imm32
331     # . . call
332     e8/call  clear-stream/disp32
333     # . . discard args
334     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
335     # . clear-stream(_test-error-stream)
336     # . . push args
337     68/push  _test-error-stream/imm32
338     # . . call
339     e8/call  clear-stream/disp32
340     # . . discard args
341     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
342     # initialize 'in'
343     # . write(_test-stream, "3")
344     # . . push args
345     68/push  "3"/imm32
346     68/push  _test-stream/imm32
347     # . . call
348     e8/call  write/disp32
349     # . . discard args
350     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
351     # initialize exit-descriptor 'ed' for the call to 'get-num' below
352     # . var ed/EAX : exit-descriptor
353     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
354     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
355     # . tailor-exit-descriptor(ed, 16)
356     # . . push args
357     68/push  0x10/imm32/nbytes-of-args-for-get-num
358     50/push-EAX/ed
359     # . . call
360     e8/call  tailor-exit-descriptor/disp32
361     # . . discard args
362     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
363     # prime the pump
364     # . get-char(_test-buffered-file)
365     # . . push args
366     68/push  _test-buffered-file/imm32
367     # . . call
368     e8/call  get-char/disp32
369     # . . discard args
370     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
371     # get-num(in, out, err, ed)
372     # . . push args
373     50/push-EAX/ed
374     68/push  _test-error-stream/imm32
375     68/push  _test-output-stream/imm32
376     68/push  _test-buffered-file/imm32
377     # . . call
378     e8/call  get-num/disp32
379     # registers except ESP may be clobbered at this point
380     # . . discard args
381     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
382     # check-ints-equal(*_test-output-stream->data, '3', msg)
383     # . . push args
384     68/push  "F - test-get-num-reads-single-digit"/imm32
385     68/push  0x33/imm32
386     b8/copy-to-EAX  _test-output-stream/imm32
387     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
388     # . . call
389     e8/call  check-ints-equal/disp32
390     # . . discard args
391     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
392     # . reclaim locals
393     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
394     5d/pop-to-EBP
395     c3/return
396 
397 test-get-num-aborts-on-non-digit-in-Look:
398     # - check that get-num returns first character if it's a digit
399     # This test uses exit-descriptors. Use EBP for setting up local variables.
400     55/push-EBP
401     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
402     # clear all streams
403     # . clear-stream(_test-stream)
404     # . . push args
405     68/push  _test-stream/imm32
406     # . . call
407     e8/call  clear-stream/disp32
408     # . . discard args
409     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
410     # . clear-stream(_test-buffered-file+4)
411     # . . push args
412     b8/copy-to-EAX  _test-buffered-file/imm32
413     05/add-to-EAX  4/imm32
414     50/push-EAX
415     # . . call
416     e8/call  clear-stream/disp32
417     # . . discard args
418     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
419     # . clear-stream(_test-output-stream)
420     # . . push args
421     68/push  _test-output-stream/imm32
422     # . . call
423     e8/call  clear-stream/disp32
424     # . . discard args
425     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
426     # . clear-stream(_test-error-stream)
427     # . . push args
428     68/push  _test-error-stream/imm32
429     # . . call
430     e8/call  clear-stream/disp32
431     # . . discard args
432     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
433     # initialize 'in'
434     # . write(_test-stream, "3")
435     # . . push args
436     68/push  "3"/imm32
437     68/push  _test-stream/imm32
438     # . . call
439     e8/call  write/disp32
440     # . . discard args
441     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
442     # initialize exit-descriptor 'ed' for the call to 'get-num' below
443     # . var ed/EAX : (address exit-descriptor)
444     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
445     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
446     # . tailor-exit-descriptor(ed, 16)
447     # . . push args
448     68/push  0x10/imm32/nbytes-of-args-for-get-num
449     50/push-EAX/ed
450     # . . call
451     e8/call  tailor-exit-descriptor/disp32
452     # . . discard args
453     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
454     # *don't* prime the pump
455     # get-num(in, out, err, ed)
456     # . . push args
457     50/push-EAX/ed
458     68/push  _test-error-stream/imm32
459     68/push  _test-output-stream/imm32
460     68/push  _test-buffered-file/imm32
461     # . . call
462     e8/call  get-num/disp32
463     # registers except ESP may be clobbered at this point
464     # . . discard args
465     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
466     # check that get-num tried to call exit(1)
467     # . check-ints-equal(ed->value, 2, msg)  # i.e. stop was called with value 1
468     # . . push args
469     68/push  "F - test-get-num-aborts-on-non-digit-in-Look"/imm32
470     68/push  2/imm32
471     # . . push ed->value
472     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
473     # . . call
474     e8/call  check-ints-equal/disp32
475     # . . discard args
476     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
477     # . reclaim locals
478     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
479     5d/pop-to-EBP
480     c3/return
481 
482 test-get-num-reads-multiple-digits:
483     # - check that get-num returns all initial digits until it encounters a non-digit
484     # This test uses exit-descriptors. Use EBP for setting up local variables.
485     55/push-EBP
486     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
487     # clear all streams
488     # . clear-stream(_test-stream)
489     # . . push args
490     68/push  _test-stream/imm32
491     # . . call
492     e8/call  clear-stream/disp32
493     # . . discard args
494     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
495     # . clear-stream(_test-buffered-file+4)
496     # . . push args
497     b8/copy-to-EAX  _test-buffered-file/imm32
498     05/add-to-EAX  4/imm32
499     50/push-EAX
500     # . . call
501     e8/call  clear-stream/disp32
502     # . . discard args
503     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
504     # . clear-stream(_test-output-stream)
505     # . . push args
506     68/push  _test-output-stream/imm32
507     # . . call
508     e8/call  clear-stream/disp32
509     # . . discard args
510     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
511     # . clear-stream(_test-error-stream)
512     # . . push args
513     68/push  _test-error-stream/imm32
514     # . . call
515     e8/call  clear-stream/disp32
516     # . . discard args
517     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
518     # initialize 'in'
519     # . write(_test-stream, "3456 x")
520     # . . push args
521     68/push  "3456"/imm32
522     68/push  _test-stream/imm32
523     # . . call
524     e8/call  write/disp32
525     # . . discard args
526     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
527     # initialize exit-descriptor 'ed' for the call to 'get-num' below
528     # . var ed/EAX : (address exit-descriptor)
529     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
530     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
531     # . tailor-exit-descriptor(ed, 16)
532     # . . push args
533     68/push  0x10/imm32/nbytes-of-args-for-get-num
534     50/push-EAX/ed
535     # . . call
536     e8/call  tailor-exit-descriptor/disp32
537     # . . discard args
538     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
539     # prime the pump
540     # . get-char(_test-buffered-file)
541     # . . push args
542     68/push  _test-buffered-file/imm32
543     # . . call
544     e8/call  get-char/disp32
545     # . . discard args
546     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
547     # get-num(in, out, err, ed)
548     # . . push args
549     50/push-EAX/ed
550     68/push  _test-error-stream/imm32
551     68/push  _test-output-stream/imm32
552     68/push  _test-buffered-file/imm32
553     # . . call
554     e8/call  get-num/disp32
555     # registers except ESP may be clobbered at this point
556     # . . discard args
557     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
558     # check-ints-equal(*_test-output-stream->data, '3456', msg)
559     # . . push args
560     68/push  "F - test-get-num-reads-multiple-digits"/imm32
561     68/push  0x36353433/imm32
562     b8/copy-to-EAX  _test-output-stream/imm32
563     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
564     # . . call
565     e8/call  check-ints-equal/disp32
566     # . . discard args
567     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
568     # . reclaim locals
569     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
570     5d/pop-to-EBP
571     c3/return
572 
573 test-get-num-reads-multiple-digits-followed-by-nondigit:
574     # - check that get-num returns all initial digits until it encounters a non-digit
575     # This test uses exit-descriptors. Use EBP for setting up local variables.
576     55/push-EBP
577     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
578     # clear all streams
579     # . clear-stream(_test-stream)
580     # . . push args
581     68/push  _test-stream/imm32
582     # . . call
583     e8/call  clear-stream/disp32
584     # . . discard args
585     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
586     # . clear-stream(_test-buffered-file+4)
587     # . . push args
588     b8/copy-to-EAX  _test-buffered-file/imm32
589     05/add-to-EAX  4/imm32
590     50/push-EAX
591     # . . call
592     e8/call  clear-stream/disp32
593     # . . discard args
594     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
595     # . clear-stream(_test-output-stream)
596     # . . push args
597     68/push  _test-output-stream/imm32
598     # . . call
599     e8/call  clear-stream/disp32
600     # . . discard args
601     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
602     # . clear-stream(_test-error-stream)
603     # . . push args
604     68/push  _test-error-stream/imm32
605     # . . call
606     e8/call  clear-stream/disp32
607     # . . discard args
608     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
609     # initialize 'in'
610     # . write(_test-stream, "3456 x")
611     # . . push args
612     68/push  "3456 x"/imm32
613     68/push  _test-stream/imm32
614     # . . call
615     e8/call  write/disp32
616     # . . discard args
617     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
618     # initialize exit-descriptor 'ed' for the call to 'get-num' below
619     # . var ed/EAX : (address exit-descriptor)
620     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
621     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
622     # . tailor-exit-descriptor(ed, 16)
623     # . . push args
624     68/push  0x10/imm32/nbytes-of-args-for-get-num
625     50/push-EAX/ed
626     # . . call
627     e8/call  tailor-exit-descriptor/disp32
628     # . . discard args
629     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
630     # prime the pump
631     # . get-char(_test-buffered-file)
632     # . . push args
633     68/push  _test-buffered-file/imm32
634     # . . call
635     e8/call  get-char/disp32
636     # . . discard args
637     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
638     # get-num(in, out, err, ed)
639     # . . push args
640     50/push-EAX/ed
641     68/push  _test-error-stream/imm32
642     68/push  _test-output-stream/imm32
643     68/push  _test-buffered-file/imm32
644     # . . call
645     e8/call  get-num/disp32
646     # registers except ESP may be clobbered at this point
647     # . . discard args
648     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
649     # check-ints-equal(*_test-output-stream->data, '3456', msg)
650     # . . push args
651     68/push  "F - test-get-num-reads-multiple-digits-followed-by-nondigit"/imm32
652     68/push  0x36353433/imm32
653     b8/copy-to-EAX  _test-output-stream/imm32
654     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
655     # . . call
656     e8/call  check-ints-equal/disp32
657     # . . discard args
658     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
659     # . reclaim locals
660     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
661     5d/pop-to-EBP
662     c3/return
663 
664 ## helpers
665 
666 # write(f, "Error: "+s+" expected\n") then stop(ed, 1)
667 expected:  # ed : (address exit-descriptor), f : fd or (address stream), s : (address array byte) -> <void>
668     # . prolog
669     55/push-EBP
670     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
671     # write(f, "Error: ")
672     # . . push args
673     68/push  "Error: "/imm32
674     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
675     # . . call
676     e8/call  write/disp32
677     # . . discard args
678     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
679     # write(f, s)
680     # . . push args
681     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
682     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
683     # . . call
684     e8/call  write/disp32
685     # . . discard args
686     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
687     # write(f, " expected\n")
688     # . . push args
689     68/push  " expected\n"/imm32
690     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
691     # . . call
692     e8/call  write/disp32
693     # . . discard args
694     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
695     # stop(ed, 1)
696     # . . push args
697     68/push  1/imm32
698     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
699     # . . call
700     e8/call  stop/disp32
701     # should never get past this point
702 $expected:dead-end:
703     # . epilog
704     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
705     5d/pop-to-EBP
706     c3/return
707 
708 # read a byte from 'f', and save it in 'Look'
709 get-char:  # f : (address buffered-file) -> <void>
710     # . prolog
711     55/push-EBP
712     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
713     # . save registers
714     50/push-EAX
715     # EAX = read-byte-buffered(f)
716     # . . push args
717     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
718     # . . call
719     e8/call  read-byte-buffered/disp32
720     # . . discard args
721     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
722     # save EAX to Look
723     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy EAX to *Look
724 $get-char:end:
725     # . restore registers
726     58/pop-to-EAX
727     # . epilog
728     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
729     5d/pop-to-EBP
730     c3/return
731 
732 is-digit?:  # c : int -> EAX : boolean
733     # . prolog
734     55/push-EBP
735     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
736     # EAX = false
737     b8/copy-to-EAX  0/imm32
738     # if (c < '0') return false
739     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x30/imm32        # compare *(EBP+8)
740     7c/jump-if-lesser  $is-digit?:end/disp8
741     # if (c > '9') return false
742     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x39/imm32        # compare *(EBP+8)
743     7f/jump-if-greater  $is-digit?:end/disp8
744     # otherwise return true
745     b8/copy-to-EAX  1/imm32
746 $is-digit?:end:
747     # . epilog
748     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
749     5d/pop-to-EBP
750     c3/return
751 
752 == data
753 
754 Look:  # (char with some extra padding)
755     0/imm32
756 
757 _test-output-stream:
758     # current write index
759     0/imm32
760     # current read index
761     0/imm32
762     # length
763     8/imm32
764     # data
765     00 00 00 00 00 00 00 00  # 8 bytes
766 
767 _test-error-stream:
768     # current write index
769     0/imm32
770     # current read index
771     0/imm32
772     # length
773     0x40/imm32
774     # data (4 lines x 16 bytes/line)
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     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
779 
780 # . . vim:nowrap:textwidth=0