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