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