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