https://github.com/akkartik/mu/blob/master/subx/apps/crenshaw2-1.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 digits.
  4 #
  5 # To run (from the subx/ directory):
  6 #   $ ./subx translate *.subx apps/crenshaw2-1.subx -o apps/crenshaw2-1
  7 #   $ echo '3'  |./subx run apps/crenshaw2-1
  8 # Expected output:
  9 #   # syscall(exit, 3)
 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 '3'  |./subx run apps/crenshaw2-1 > z1.subx
 16 #   $ ./subx translate z1.subx -o z1
 17 #   $ ./subx run z1
 18 #   $ echo $?
 19 #   3
 20 #
 21 # Stdin must contain just a single hex digit. Other input will print an error:
 22 #   $ echo 'xyz'  |./subx run apps/crenshaw2-1
 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-aborts-on-non-digit-in-Look/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 single digit into 'out'. Abort if there are none, or if there is no space in 'out'.
199 # Input comes from the global variable 'Look', and we leave the next byte from
200 # 'in' into it 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     #   if out->write >= out->length
205     #     write(err, "Error: too many digits in number\n")
206     #     stop(ed, 1)
207     #   out->data[out->write] = LSB(Look)
208     #   ++out->write
209     #   Look = get-char(in)
210     #
211     # registers:
212     #   in: ESI
213     #   out: EDI
214     #   out->write: ECX (cached copy; need to keep in sync)
215     #   out->length: EDX
216     #   temporaries: EAX, EBX
217     # We can't allocate Look to a register because it gets written implicitly in
218     # get-char in each iteration of the loop. (Thereby demonstrating that it's
219     # not the right interface for us. But we'll keep it just to follow Crenshaw.)
220     #
221     # . prolog
222     55/push-EBP
223     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
224     # - if (is-digit?(Look)) expected(ed, err, "integer")
225     # . EAX = is-digit?(Look)
226     # . . push args
227     ff          6/subop/push        0/mod/indirect  5/rm32/.disp32            .             .           .           Look/disp32     .                 # push *Look
228     # . . call
229     e8/call  is-digit?/disp32
230     # . . discard args
231     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
232     # . if (EAX == 0)
233     3d/compare-EAX-and  0/imm32
234     75/jump-if-not-equal  $get-num:main/disp8
235     # . expected(ed, err, "integer")
236     # . . push args
237     68/push  "integer"/imm32
238     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
239     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
240     # . . call
241     e8/call  expected/disp32  # never returns
242     # . . discard args
243     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
244 $get-num:main:
245     # - otherwise read a digit
246     # . save registers
247     50/push-EAX
248     51/push-ECX
249     52/push-EDX
250     53/push-EBX
251     56/push-ESI
252     57/push-EDI
253     # read necessary variables to registers
254     # ESI = in
255     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           6/r32/ESI   8/disp8         .                 # copy *(EBP+8) to ESI
256     # EDI = out
257     8b/copy                         1/mod/*+disp8   5/rm32/EBP    .           .             .           7/r32/EDI   0xc/disp8       .                 # copy *(EBP+12) to EDI
258     # ECX = out->write
259     8b/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy *EDI to ECX
260     # EDX = out->length
261     8b/copy                         1/mod/*+disp8   7/rm32/EDI    .           .             .           2/r32/EDX   8/disp8         .                 # copy *(EDI+8) to EDX
262     # if (out->write >= out->length) error
263     39/compare                      3/mod/direct    2/rm32/EDX    .           .             .           1/r32/ECX   .               .                 # compare EDX with ECX
264     7d/jump-if-lesser  $get-num:stage2/disp8
265     # . error(ed, err, msg)  # TODO: show full number
266     # . . push args
267     68/push  "get-num: too many digits in number"/imm32
268     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
269     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x14/disp8      .                 # push *(EBP+20)
270     # . . call
271     e8/call  error/disp32  # never returns
272     # . . discard args
273     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
274 $get-num:stage2:
275     # out->data[out->write] = LSB(Look)
276     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
277     8b/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy *Look to EAX
278     88/copy-byte                    0/mod/indirect  3/rm32/EBX    .           .             .           0/r32/AL    .               .                 # copy byte at AL to *EBX
279     # ++out->write
280     41/increment-ECX
281     # Look = get-char(in)
282     # . . push args
283     56/push-ESI
284     # . . call
285     e8/call  get-char/disp32
286     # . . discard args
287     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
288 $get-num:loop-end:
289     # persist necessary variables from registers
290     89/copy                         0/mod/indirect  7/rm32/EDI    .           .             .           1/r32/ECX   .               .                 # copy ECX to *EDI
291 $get-num:end:
292     # . restore registers
293     5f/pop-to-EDI
294     5e/pop-to-ESI
295     5b/pop-to-EBX
296     5a/pop-to-EDX
297     59/pop-to-ECX
298     58/pop-to-EAX
299     # . epilog
300     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
301     5d/pop-to-EBP
302     c3/return
303 
304 test-get-num-reads-single-digit:
305     # - check that get-num returns first character if it's a digit
306     # This test uses exit-descriptors. Use EBP for setting up local variables.
307     55/push-EBP
308     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
309     # clear all streams
310     # . clear-stream(_test-stream)
311     # . . push args
312     68/push  _test-stream/imm32
313     # . . call
314     e8/call  clear-stream/disp32
315     # . . discard args
316     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
317     # . clear-stream(_test-buffered-file+4)
318     # . . push args
319     b8/copy-to-EAX  _test-buffered-file/imm32
320     05/add-to-EAX  4/imm32
321     50/push-EAX
322     # . . call
323     e8/call  clear-stream/disp32
324     # . . discard args
325     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
326     # . clear-stream(_test-output-stream)
327     # . . push args
328     68/push  _test-output-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-error-stream)
334     # . . push args
335     68/push  _test-error-stream/imm32
336     # . . call
337     e8/call  clear-stream/disp32
338     # . . discard args
339     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
340     # initialize 'in'
341     # . write(_test-stream, "3")
342     # . . push args
343     68/push  "3"/imm32
344     68/push  _test-stream/imm32
345     # . . call
346     e8/call  write/disp32
347     # . . discard args
348     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
349     # initialize exit-descriptor 'ed' for the call to 'get-num' below
350     # . var ed/EAX : exit-descriptor
351     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
352     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
353     # . tailor-exit-descriptor(ed, 16)
354     # . . push args
355     68/push  0x10/imm32/nbytes-of-args-for-get-num
356     50/push-EAX/ed
357     # . . call
358     e8/call  tailor-exit-descriptor/disp32
359     # . . discard args
360     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
361     # prime the pump
362     # . get-char(_test-buffered-file)
363     # . . push args
364     68/push  _test-buffered-file/imm32
365     # . . call
366     e8/call  get-char/disp32
367     # . . discard args
368     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
369     # get-num(in, out, err, ed)
370     # . . push args
371     50/push-EAX/ed
372     68/push  _test-error-stream/imm32
373     68/push  _test-output-stream/imm32
374     68/push  _test-buffered-file/imm32
375     # . . call
376     e8/call  get-num/disp32
377     # registers except ESP may be clobbered at this point
378     # . . discard args
379     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
380     # check-ints-equal(*_test-output-stream->data, '3', msg)
381     # . . push args
382     68/push  "F - test-get-num-reads-single-digit"/imm32
383     68/push  0x33/imm32
384     b8/copy-to-EAX  _test-output-stream/imm32
385     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           0xc/disp8       .                 # push *(EAX+12)
386     # . . call
387     e8/call  check-ints-equal/disp32
388     # . . discard args
389     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
390     # . reclaim locals
391     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
392     5d/pop-to-EBP
393     c3/return
394 
395 test-get-num-aborts-on-non-digit-in-Look:
396     # - check that get-num returns first character if it's a digit
397     # This test uses exit-descriptors. Use EBP for setting up local variables.
398     55/push-EBP
399     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
400     # clear all streams
401     # . clear-stream(_test-stream)
402     # . . push args
403     68/push  _test-stream/imm32
404     # . . call
405     e8/call  clear-stream/disp32
406     # . . discard args
407     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
408     # . clear-stream(_test-buffered-file+4)
409     # . . push args
410     b8/copy-to-EAX  _test-buffered-file/imm32
411     05/add-to-EAX  4/imm32
412     50/push-EAX
413     # . . call
414     e8/call  clear-stream/disp32
415     # . . discard args
416     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
417     # . clear-stream(_test-output-stream)
418     # . . push args
419     68/push  _test-output-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-error-stream)
425     # . . push args
426     68/push  _test-error-stream/imm32
427     # . . call
428     e8/call  clear-stream/disp32
429     # . . discard args
430     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
431     # initialize 'in'
432     # . write(_test-stream, "3")
433     # . . push args
434     68/push  "3"/imm32
435     68/push  _test-stream/imm32
436     # . . call
437     e8/call  write/disp32
438     # . . discard args
439     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
440     # initialize exit-descriptor 'ed' for the call to 'get-num' below
441     # . var ed/EAX : (address exit-descriptor)
442     81          5/subop/subtract    3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # subtract from ESP
443     89/copy                         3/mod/direct    0/rm32/EAX    .           .             .           4/r32/ESP   .               .                 # copy ESP to EAX
444     # . tailor-exit-descriptor(ed, 16)
445     # . . push args
446     68/push  0x10/imm32/nbytes-of-args-for-get-num
447     50/push-EAX/ed
448     # . . call
449     e8/call  tailor-exit-descriptor/disp32
450     # . . discard args
451     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
452     # *don't* prime the pump
453     # get-num(in, out, err, ed)
454     # . . push args
455     50/push-EAX/ed
456     68/push  _test-error-stream/imm32
457     68/push  _test-output-stream/imm32
458     68/push  _test-buffered-file/imm32
459     # . . call
460     e8/call  get-num/disp32
461     # registers except ESP may be clobbered at this point
462     # . . discard args
463     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0x10/imm32        # add to ESP
464     # check that get-num tried to call exit(1)
465     # . check-ints-equal(ed->value, 2, msg)  # i.e. stop was called with value 1
466     # . . push args
467     68/push  "F - test-get-num-aborts-on-non-digit-in-Look"/imm32
468     68/push  2/imm32
469     # . . push ed->value
470     ff          6/subop/push        1/mod/*+disp8   0/rm32/EAX    .           .             .           .           4/disp8         .                 # push *(EAX+4)
471     # . . call
472     e8/call  check-ints-equal/disp32
473     # . . discard args
474     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               0xc/imm32         # add to ESP
475     # . reclaim locals
476     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
477     5d/pop-to-EBP
478     c3/return
479 
480 ## helpers
481 
482 # write(f, "Error: "+s+" expected\n") then stop(ed, 1)
483 expected:  # ed : (address exit-descriptor), f : fd or (address stream), s : (address array byte) -> <void>
484     # . prolog
485     55/push-EBP
486     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
487     # write(f, "Error: ")
488     # . . push args
489     68/push  "Error: "/imm32
490     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
491     # . . call
492     e8/call  write/disp32
493     # . . discard args
494     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
495     # write(f, s)
496     # . . push args
497     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0x10/disp8      .                 # push *(EBP+16)
498     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
499     # . . call
500     e8/call  write/disp32
501     # . . discard args
502     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
503     # write(f, " expected")
504     # . . push args
505     68/push  " expected"/imm32
506     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
507     # . . call
508     e8/call  write/disp32
509     # . . discard args
510     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
511     # write(f, Newline)
512     # . . push args
513     68/push  Newline/imm32
514     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           0xc/disp8       .                 # push *(EBP+12)
515     # . . call
516     e8/call  write/disp32
517     # . . discard args
518     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               8/imm32           # add to ESP
519     # stop(ed, 1)
520     # . . push args
521     68/push  1/imm32
522     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
523     # . . call
524     e8/call  stop/disp32
525     # should never get past this point
526 $expected:dead-end:
527     # . epilog
528     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
529     5d/pop-to-EBP
530     c3/return
531 
532 # read a byte from 'f', and save it in 'Look'
533 get-char:  # f : (address buffered-file) -> <void>
534     # . prolog
535     55/push-EBP
536     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
537     # . save registers
538     50/push-EAX
539     # read-byte(f)
540     # . . push args
541     ff          6/subop/push        1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         .                 # push *(EBP+8)
542     # . . call
543     e8/call  read-byte/disp32
544     # . . discard args
545     81          0/subop/add         3/mod/direct    4/rm32/ESP    .           .             .           .           .               4/imm32           # add to ESP
546     # save EAX to Look
547     89/copy                         0/mod/indirect  5/rm32/.disp32            .             .           0/r32/EAX   Look/disp32     .                 # copy EAX to *Look
548 $get-char:end:
549     # . restore registers
550     58/pop-to-EAX
551     # . epilog
552     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
553     5d/pop-to-EBP
554     c3/return
555 
556 is-digit?:  # c : int -> EAX : boolean
557     # . prolog
558     55/push-EBP
559     89/copy                         3/mod/direct    5/rm32/EBP    .           .             .           4/r32/ESP   .               .                 # copy ESP to EBP
560     # EAX = false
561     b8/copy-to-EAX  0/imm32
562     # if (c < '0') return false
563     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x30/imm32        # compare *(EBP+8)
564     7c/jump-if-lesser  $is-digit?:end/disp8
565     # if (c > '9') return false
566     81          7/subop/compare     1/mod/*+disp8   5/rm32/EBP    .           .             .           .           8/disp8         0x39/imm32        # compare *(EBP+8)
567     7f/jump-if-greater  $is-digit?:end/disp8
568     # otherwise return true
569     b8/copy-to-EAX  1/imm32
570 $is-digit?:end:
571     # . epilog
572     89/copy                         3/mod/direct    4/rm32/ESP    .           .             .           5/r32/EBP   .               .                 # copy EBP to ESP
573     5d/pop-to-EBP
574     c3/return
575 
576 == data
577 
578 Look:  # (char with some extra padding)
579     0/imm32
580 
581 _test-output-stream:
582     # current write index
583     0/imm32
584     # current read index
585     0/imm32
586     # length
587     8/imm32
588     # data
589     00 00 00 00 00 00 00 00  # 8 bytes
590 
591 _test-error-stream:
592     # current write index
593     0/imm32
594     # current read index
595     0/imm32
596     # length
597     0x40/imm32
598     # data (4 lines x 16 bytes/line)
599     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
600     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
601     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
602     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
603 
604 # . . vim:nowrap:textwidth=0