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