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