https://github.com/akkartik/mu/blob/master/067parse-hex.subx
  1 # some utilities for converting numbers from hex
  2 # lowercase letters only for now
  3 
  4 == code
  5 #   instruction                     effective address                                                   register    displacement    immediate
  6 # . op          subop               mod             rm32          base        index         scale       r32
  7 # . 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
  8 
  9 is-hex-int?:  # in : (address slice) -> eax : boolean
 10     # . prologue
 11     55/push-ebp
 12     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 13     # . save registers
 14     51/push-ecx
 15     52/push-edx
 16     53/push-ebx
 17     # ecx = s
 18     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
 19     # edx = s->end
 20     8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           2/r32/edx   4/disp8         .                 # copy *(ecx+4) to edx
 21     # var curr/ecx : (address byte) = s->start
 22     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # copy *ecx to ecx
 23     # if s is empty return false
 24     b8/copy-to-eax  0/imm32/false
 25     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
 26     73/jump-if-greater-or-equal-unsigned  $is-hex-int?:end/disp8
 27     # skip past leading '-'
 28     # . if (*curr == '-') ++curr
 29     31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
 30     8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           3/r32/BL    .               .                 # copy byte at *ecx to BL
 31     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x2d/imm32/-      # compare ebx
 32     75/jump-if-not-equal  $is-hex-int?:initial-0/disp8
 33     # . ++curr
 34     41/increment-ecx
 35     # skip past leading '0x'
 36 $is-hex-int?:initial-0:
 37     # . if (*curr != '0') jump to loop
 38     31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
 39     8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           3/r32/BL    .               .                 # copy byte at *ecx to BL
 40     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x30/imm32/0      # compare ebx
 41     75/jump-if-not-equal  $is-hex-int?:loop/disp8
 42     # . ++curr
 43     41/increment-ecx
 44 $is-hex-int?:initial-0x:
 45     # . if (curr >= in->end) return true
 46     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
 47     73/jump-if-greater-or-equal-unsigned  $is-hex-int?:true/disp8
 48     # . if (*curr != 'x') jump to loop  # the previous '0' is still valid so doesn't need to be checked again
 49     31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
 50     8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           3/r32/BL    .               .                 # copy byte at *ecx to BL
 51     81          7/subop/compare     3/mod/direct    3/rm32/ebx    .           .             .           .           .               0x78/imm32/x      # compare ebx
 52     75/jump-if-not-equal  $is-hex-int?:loop/disp8
 53     # . ++curr
 54     41/increment-ecx
 55 $is-hex-int?:loop:
 56     # if (curr >= in->end) return true
 57     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
 58     73/jump-if-greater-or-equal-unsigned  $is-hex-int?:true/disp8
 59     # var eax : boolean = is-hex-digit?(*curr)
 60     # . . push args
 61     8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
 62     50/push-eax
 63     # . . call
 64     e8/call  is-hex-digit?/disp32
 65     # . . discard args
 66     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 67     # if (eax == false) return false
 68     3d/compare-eax-and  0/imm32
 69     74/jump-if-equal  $is-hex-int?:end/disp8
 70     # ++curr
 71     41/increment-ecx
 72     # loop
 73     eb/jump  $is-hex-int?:loop/disp8
 74 $is-hex-int?:true:
 75     # return true
 76     b8/copy-to-eax  1/imm32/true
 77 $is-hex-int?:end:
 78     # . restore registers
 79     5b/pop-to-ebx
 80     5a/pop-to-edx
 81     59/pop-to-ecx
 82     # . epilogue
 83     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
 84     5d/pop-to-ebp
 85     c3/return
 86 
 87 test-is-hex-int:
 88     # . prologue
 89     55/push-ebp
 90     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 91     # (eax..ecx) = "34"
 92     b8/copy-to-eax  "34"/imm32
 93     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
 94     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
 95     05/add-to-eax  4/imm32
 96     # var slice/ecx : (ref slice) = {eax, ecx}
 97     51/push-ecx
 98     50/push-eax
 99     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
100     # eax = is-hex-int?(slice)
101     # . . push args
102     51/push-ecx
103     # . . call
104     e8/call  is-hex-int?/disp32
105     # . . discard args
106     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
107     # check-ints-equal(eax, 1, msg)
108     # . . push args
109     68/push  "F - test-is-hex-int"/imm32
110     68/push  1/imm32/true
111     50/push-eax
112     # . . call
113     e8/call  check-ints-equal/disp32
114     # . . discard args
115     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
116     # . epilogue
117     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
118     5d/pop-to-ebp
119     c3/return
120 
121 test-is-hex-int-handles-letters:
122     # . prologue
123     55/push-ebp
124     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
125     # (eax..ecx) = "34a"
126     b8/copy-to-eax  "34a"/imm32
127     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
128     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
129     05/add-to-eax  4/imm32
130     # var slice/ecx : (ref slice) = {eax, ecx}
131     51/push-ecx
132     50/push-eax
133     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
134     # eax = is-hex-int?(slice)
135     # . . push args
136     51/push-ecx
137     # . . call
138     e8/call  is-hex-int?/disp32
139     # . . discard args
140     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
141     # check-ints-equal(eax, 1, msg)
142     # . . push args
143     68/push  "F - test-is-hex-int-handles-letters"/imm32
144     68/push  1/imm32/true
145     50/push-eax
146     # . . call
147     e8/call  check-ints-equal/disp32
148     # . . discard args
149     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
150     # . epilogue
151     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
152     5d/pop-to-ebp
153     c3/return
154 
155 test-is-hex-int-with-trailing-char:
156     # . prologue
157     55/push-ebp
158     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
159     # (eax..ecx) = "34q"
160     b8/copy-to-eax  "34q"/imm32
161     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
162     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
163     05/add-to-eax  4/imm32
164     # var slice/ecx : (ref slice) = {eax, ecx}
165     51/push-ecx
166     50/push-eax
167     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
168     # eax = is-hex-int?(slice)
169     # . . push args
170     51/push-ecx
171     # . . call
172     e8/call  is-hex-int?/disp32
173     # . . discard args
174     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
175     # check-ints-equal(eax, 0, msg)
176     # . . push args
177     68/push  "F - test-is-hex-int-with-trailing-char"/imm32
178     68/push  0/imm32/false
179     50/push-eax
180     # . . call
181     e8/call  check-ints-equal/disp32
182     # . . discard args
183     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
184     # . epilogue
185     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
186     5d/pop-to-ebp
187     c3/return
188 
189 test-is-hex-int-with-leading-char:
190     # . prologue
191     55/push-ebp
192     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
193     # (eax..ecx) = "q34"
194     b8/copy-to-eax  "q34"/imm32
195     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
196     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
197     05/add-to-eax  4/imm32
198     # var slice/ecx : (ref slice) = {eax, ecx}
199     51/push-ecx
200     50/push-eax
201     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
202     # eax = is-hex-int?(slice)
203     # . . push args
204     51/push-ecx
205     # . . call
206     e8/call  is-hex-int?/disp32
207     # . . discard args
208     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
209     # check-ints-equal(eax, 0, msg)
210     # . . push args
211     68/push  "F - test-is-hex-int-with-leading-char"/imm32
212     68/push  0/imm32/false
213     50/push-eax
214     # . . call
215     e8/call  check-ints-equal/disp32
216     # . . discard args
217     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
218     # . epilogue
219     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
220     5d/pop-to-ebp
221     c3/return
222 
223 test-is-hex-int-empty:
224     # . prologue
225     55/push-ebp
226     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
227     # var slice/ecx : (ref slice) = ""
228     68/push  0/imm32
229     68/push  0/imm32
230     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
231     # eax = is-hex-int?(slice)
232     # . . push args
233     51/push-ecx
234     # . . call
235     e8/call  is-hex-int?/disp32
236     # . . discard args
237     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
238     # check-ints-equal(eax, 0, msg)
239     # . . push args
240     68/push  "F - test-is-hex-int-empty"/imm32
241     68/push  0/imm32/false
242     50/push-eax
243     # . . call
244     e8/call  check-ints-equal/disp32
245     # . . discard args
246     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
247     # . epilogue
248     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
249     5d/pop-to-ebp
250     c3/return
251 
252 test-is-hex-int-handles-0x-prefix:
253     # . prologue
254     55/push-ebp
255     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
256     # (eax..ecx) = "0x3a"
257     b8/copy-to-eax  "0x3a"/imm32
258     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
259     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
260     05/add-to-eax  4/imm32
261     # var slice/ecx : (ref slice) = {eax, ecx}
262     51/push-ecx
263     50/push-eax
264     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
265     # eax = is-hex-int?(slice)
266     # . . push args
267     51/push-ecx
268     # . . call
269     e8/call  is-hex-int?/disp32
270     # . . discard args
271     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
272     # check-ints-equal(eax, 1, msg)
273     # . . push args
274     68/push  "F - test-is-hex-int-handles-0x-prefix"/imm32
275     68/push  1/imm32/true
276     50/push-eax
277     # . . call
278     e8/call  check-ints-equal/disp32
279     # . . discard args
280     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
281     # . epilogue
282     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
283     5d/pop-to-ebp
284     c3/return
285 
286 test-is-hex-int-handles-negative:
287     # . prologue
288     55/push-ebp
289     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
290     # (eax..ecx) = "-34a"
291     b8/copy-to-eax  "-34a"/imm32
292     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
293     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
294     05/add-to-eax  4/imm32
295     # var slice/ecx : (ref slice) = {eax, ecx}
296     51/push-ecx
297     50/push-eax
298     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
299     # eax = is-hex-int?(slice)
300     # . . push args
301     51/push-ecx
302     # . . call
303     e8/call  is-hex-int?/disp32
304     # . . discard args
305     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
306     # check-ints-equal(eax, 1, msg)
307     # . . push args
308     68/push  "F - test-is-hex-int-handles-negative"/imm32
309     68/push  1/imm32/true
310     50/push-eax
311     # . . call
312     e8/call  check-ints-equal/disp32
313     # . . discard args
314     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
315     # . epilogue
316     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
317     5d/pop-to-ebp
318     c3/return
319 
320 test-is-hex-int-handles-negative-0x-prefix:
321     # . prologue
322     55/push-ebp
323     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
324     # (eax..ecx) = "-0x3a"
325     b8/copy-to-eax  "-0x3a"/imm32
326     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
327     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
328     05/add-to-eax  4/imm32
329     # var slice/ecx : (ref slice) = {eax, ecx}
330     51/push-ecx
331     50/push-eax
332     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
333     # eax = is-hex-int?(slice)
334     # . . push args
335     51/push-ecx
336     # . . call
337     e8/call  is-hex-int?/disp32
338     # . . discard args
339     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
340     # check-ints-equal(eax, 1, msg)
341     # . . push args
342     68/push  "F - test-is-hex-int-handles-negative-0x-prefix"/imm32
343     68/push  1/imm32/true
344     50/push-eax
345     # . . call
346     e8/call  check-ints-equal/disp32
347     # . . discard args
348     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
349     # . epilogue
350     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
351     5d/pop-to-ebp
352     c3/return
353 
354 parse-hex-int:  # in : (address slice) -> result/eax : int
355     # . prologue
356     55/push-ebp
357     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
358     # . save registers
359     51/push-ecx
360     52/push-edx
361     53/push-ebx
362     56/push-esi
363     # var result/ebx : int = 0
364     31/xor                          3/mod/direct    3/rm32/ebx    .           .             .           3/r32/ebx   .               .                 # clear ebx
365     # ecx = in
366     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
367     # edx = in->end
368     8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           2/r32/edx   4/disp8         .                 # copy *(ecx+4) to edx
369     # var curr/ecx : (address byte) = in->start
370     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # copy *ecx to ecx
371     # var negate?/esi : boolean = false
372     31/xor                          3/mod/direct    6/rm32/esi    .           .             .           6/r32/esi   .               .                 # clear esi
373 $parse-hex-int:negative:
374     # if (*curr == '-') ++curr, negate = true
375     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
376     8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
377     3d/compare-eax-and  0x2d/imm32/-
378     75/jump-if-not-equal  $parse-hex-int:initial-0/disp8
379     # . ++curr
380     41/increment-ecx
381     # . negate = true
382     be/copy-to-esi  1/imm32/true
383 $parse-hex-int:initial-0:
384     # skip past leading '0x'
385     # . if (*curr != '0') jump to loop
386     8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
387     3d/compare-eax-and  0x30/imm32/0
388     75/jump-if-not-equal  $parse-hex-int:loop/disp8
389     # . ++curr
390     41/increment-ecx
391 $parse-hex-int:initial-0x:
392     # . if (curr >= in->end) return result
393     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
394     73/jump-if-greater-or-equal-unsigned  $parse-hex-int:end/disp8
395     # . if (*curr != 'x') jump to loop  # the previous '0' is still valid so doesn't need to be checked again
396     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
397     8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
398     3d/compare-eax-and  0x78/imm32/x
399     75/jump-if-not-equal  $parse-hex-int:loop/disp8
400     # . ++curr
401     41/increment-ecx
402 $parse-hex-int:loop:
403     # if (curr >= in->end) break
404     39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # compare ecx with edx
405     73/jump-if-greater-or-equal-unsigned  $parse-hex-int:negate/disp8
406     # var eax : int = from-hex-char(*curr)
407     # . . copy arg to eax
408     8a/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy byte at *ecx to AL
409     # . . call
410     e8/call  from-hex-char/disp32
411     # result = result * 16 + eax
412     c1/shift    4/subop/left        3/mod/direct    3/rm32/ebx    .           .             .           .           .               4/imm8            # shift ebx left by 4 bits
413     01/add                          3/mod/direct    3/rm32/ebx    .           .             .           0/r32/eax   .               .                 # add eax to ebx
414     # ++curr
415     41/increment-ecx
416     # loop
417     eb/jump  $parse-hex-int:loop/disp8
418 $parse-hex-int:negate:
419     # if (negate?) result = -result
420     81          7/subop/compare     3/mod/direct    6/rm32/esi    .           .             .           .           .               0/imm32/false     # compare esi
421     74/jump-if-equal  $parse-hex-int:end/disp8
422     f7          3/subop/negate      3/mod/direct    3/rm32/ebx    .           .             .           .           .               .                 # negate ebx
423 $parse-hex-int:end:
424     # return result
425     89/copy                         3/mod/direct    0/rm32/eax    .           .             .           3/r32/ebx   .               .                 # copy ebx to eax
426     # . restore registers
427     5e/pop-to-esi
428     5b/pop-to-ebx
429     5a/pop-to-edx
430     59/pop-to-ecx
431     # . epilogue
432     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
433     5d/pop-to-ebp
434     c3/return
435 
436 test-parse-hex-int-single-digit:
437     # . prologue
438     55/push-ebp
439     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
440     # (eax..ecx) = "a"
441     b8/copy-to-eax  "a"/imm32
442     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
443     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
444     05/add-to-eax  4/imm32
445     # var slice/ecx : (ref slice) = {eax, ecx}
446     51/push-ecx
447     50/push-eax
448     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
449     # eax = parse-hex-int(slice)
450     # . . push args
451     51/push-ecx
452     # . . call
453     e8/call  parse-hex-int/disp32
454     # . . discard args
455     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
456     # check-ints-equal(eax, 0xa, msg)
457     # . . push args
458     68/push  "F - test-parse-hex-int-single-digit"/imm32
459     68/push  0xa/imm32
460     50/push-eax
461     # . . call
462     e8/call  check-ints-equal/disp32
463     # . . discard args
464     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
465     # . epilogue
466     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
467     5d/pop-to-ebp
468     c3/return
469 
470 test-parse-hex-int-multi-digit:
471     # . prologue
472     55/push-ebp
473     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
474     # (eax..ecx) = "34a"
475     b8/copy-to-eax  "34a"/imm32
476     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
477     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
478     05/add-to-eax  4/imm32
479     # var slice/ecx : (ref slice) = {eax, ecx}
480     51/push-ecx
481     50/push-eax
482     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
483     # eax = parse-hex-int(slice)
484     # . . push args
485     51/push-ecx
486     # . . call
487     e8/call  parse-hex-int/disp32
488     # . . discard args
489     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
490     # check-ints-equal(eax, 0x34a, msg)
491     # . . push args
492     68/push  "F - test-parse-hex-int-multi-digit"/imm32
493     68/push  0x34a/imm32
494     50/push-eax
495     # . . call
496     e8/call  check-ints-equal/disp32
497     # . . discard args
498     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
499     # . epilogue
500     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
501     5d/pop-to-ebp
502     c3/return
503 
504 test-parse-hex-int-0x-prefix:
505     # . prologue
506     55/push-ebp
507     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
508     # (eax..ecx) = "0x34"
509     b8/copy-to-eax  "0x34"/imm32
510     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
511     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
512     05/add-to-eax  4/imm32
513     # var slice/ecx : (ref slice) = {eax, ecx}
514     51/push-ecx
515     50/push-eax
516     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
517     # eax = parse-hex-int(slice)
518     # . . push args
519     51/push-ecx
520     # . . call
521     e8/call  parse-hex-int/disp32
522     # . . discard args
523     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
524     # check-ints-equal(eax, 0x34a, msg)
525     # . . push args
526     68/push  "F - test-parse-hex-int-0x-prefix"/imm32
527     68/push  0x34/imm32
528     50/push-eax
529     # . . call
530     e8/call  check-ints-equal/disp32
531     # . . discard args
532     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
533     # . epilogue
534     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
535     5d/pop-to-ebp
536     c3/return
537 
538 test-parse-hex-int-zero:
539     # . prologue
540     55/push-ebp
541     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
542     # (eax..ecx) = "0"
543     b8/copy-to-eax  "0"/imm32
544     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
545     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
546     05/add-to-eax  4/imm32
547     # var slice/ecx : (ref slice) = {eax, ecx}
548     51/push-ecx
549     50/push-eax
550     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
551     # eax = parse-hex-int(slice)
552     # . . push args
553     51/push-ecx
554     # . . call
555     e8/call  parse-hex-int/disp32
556     # . . discard args
557     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
558     # check-ints-equal(eax, 0x34a, msg)
559     # . . push args
560     68/push  "F - test-parse-hex-int-zero"/imm32
561     68/push  0/imm32
562     50/push-eax
563     # . . call
564     e8/call  check-ints-equal/disp32
565     # . . discard args
566     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
567     # . epilogue
568     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
569     5d/pop-to-ebp
570     c3/return
571 
572 test-parse-hex-int-0-prefix:
573     # . prologue
574     55/push-ebp
575     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
576     # (eax..ecx) = "03"
577     b8/copy-to-eax  "03"/imm32
578     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
579     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
580     05/add-to-eax  4/imm32
581     # var slice/ecx : (ref slice) = {eax, ecx}
582     51/push-ecx
583     50/push-eax
584     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
585     # eax = parse-hex-int(slice)
586     # . . push args
587     51/push-ecx
588     # . . call
589     e8/call  parse-hex-int/disp32
590     # . . discard args
591     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
592     # check-ints-equal(eax, 0x3, msg)
593     # . . push args
594     68/push  "F - test-parse-hex-int-0-prefix"/imm32
595     68/push  0x3/imm32
596     50/push-eax
597     # . . call
598     e8/call  check-ints-equal/disp32
599     # . . discard args
600     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
601     # . epilogue
602     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
603     5d/pop-to-ebp
604     c3/return
605 
606 test-parse-hex-int-negative:
607     # . prologue
608     55/push-ebp
609     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
610     # (eax..ecx) = "-03"
611     b8/copy-to-eax  "-03"/imm32
612     8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           1/r32/ecx   .               .                 # copy *eax to ecx
613     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    0/base/eax  1/index/ecx   .           1/r32/ecx   4/disp8         .                 # copy eax+ecx+4 to ecx
614     05/add-to-eax  4/imm32
615     # var slice/ecx : (ref slice) = {eax, ecx}
616     51/push-ecx
617     50/push-eax
618     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
619     # eax = parse-hex-int(slice)
620     # . . push args
621     51/push-ecx
622     # . . call
623     e8/call  parse-hex-int/disp32
624     # . . discard args
625     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
626     # check-ints-equal(eax, -3, msg)
627     # . . push args
628     68/push  "F - test-parse-hex-int-negative"/imm32
629     68/push  -3/imm32
630     50/push-eax
631     # . . call
632     e8/call  check-ints-equal/disp32
633     # . . discard args
634     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
635     # . epilogue
636     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
637     5d/pop-to-ebp
638     c3/return
639 
640 is-hex-digit?:  # c : byte -> eax : boolean
641     # . prologue
642     55/push-ebp
643     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
644     # . save registers
645     51/push-ecx
646     # ecx = c
647     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
648     # return false if c < '0'
649     81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x30/imm32        # compare ecx
650     7c/jump-if-lesser  $is-hex-digit?:false/disp8
651     # return true if c <= '9'
652     81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x39/imm32        # compare ecx
653     7e/jump-if-lesser-or-equal  $is-hex-digit?:true/disp8
654     # drop case
655     25/and-eax-with 0x5f/imm32
656     # return false if c > 'f'
657     81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x66/imm32        # compare ecx
658     7f/jump-if-greater  $is-hex-digit?:false/disp8
659     # return true if c >= 'a'
660     81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x61/imm32        # compare ecx
661     7d/jump-if-greater-or-equal  $is-hex-digit?:true/disp8
662     # otherwise return false
663 $is-hex-digit?:false:
664     b8/copy-to-eax  0/imm32/false
665     eb/jump $is-hex-digit?:end/disp8
666 $is-hex-digit?:true:
667     b8/copy-to-eax  1/imm32/true
668 $is-hex-digit?:end:
669     # . restore registers
670     59/pop-to-ecx
671     # . epilogue
672     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
673     5d/pop-to-ebp
674     c3/return
675 
676 test-hex-below-0:
677     # eax = is-hex-digit?(0x2f)
678     # . . push args
679     68/push  0x2f/imm32
680     # . . call
681     e8/call  is-hex-digit?/disp32
682     # . . discard args
683     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
684     # check-ints-equal(eax, 0, msg)
685     # . . push args
686     68/push  "F - test-hex-below-0"/imm32
687     68/push  0/imm32/false
688     50/push-eax
689     # . . call
690     e8/call  check-ints-equal/disp32
691     # . . discard args
692     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
693     c3/return
694 
695 test-hex-0-to-9:
696     # eax = is-hex-digit?(0x30)
697     # . . push args
698     68/push  0x30/imm32
699     # . . call
700     e8/call  is-hex-digit?/disp32
701     # . . discard args
702     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
703     # check-ints-equal(eax, 1, msg)
704     # . . push args
705     68/push  "F - test-hex-at-0"/imm32
706     68/push  1/imm32/true
707     50/push-eax
708     # . . call
709     e8/call  check-ints-equal/disp32
710     # . . discard args
711     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
712     # eax = is-hex-digit?(0x39)
713     # . . push args
714     68/push  0x39/imm32
715     # . . call
716     e8/call  is-hex-digit?/disp32
717     # . . discard args
718     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
719     # check-ints-equal(eax, 1, msg)
720     # . . push args
721     68/push  "F - test-hex-at-9"/imm32
722     68/push  1/imm32/true
723     50/push-eax
724     # . . call
725     e8/call  check-ints-equal/disp32
726     # . . discard args
727     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
728     c3/return
729 
730 test-hex-above-9-to-a:
731     # eax = is-hex-digit?(0x3a)
732     # . . push args
733     68/push  0x3a/imm32
734     # . . call
735     e8/call  is-hex-digit?/disp32
736     # . . discard args
737     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
738     # check-ints-equal(eax, 0, msg)
739     # . . push args
740     68/push  "F - test-hex-above-9-to-a"/imm32
741     68/push  0/imm32/false
742     50/push-eax
743     # . . call
744     e8/call  check-ints-equal/disp32
745     # . . discard args
746     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
747     c3/return
748 
749 test-hex-a-to-f:
750     # eax = is-hex-digit?(0x61)
751     # . . push args
752     68/push  0x61/imm32
753     pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
## deleting sandboxes

scenario deleting-sandboxes [
  trace-until 100/app  # trace too long
  assume-screen 100/width, 15/height
  1:text <- new []
  2:text <- new []
  3:&:environment <- new-programming-environment screen:&:screen, 1:text, 2:text
  # run a few commands
  assume-console [
    left-click 1, 80
    type [divide-with-remainder 11, 3]
    press F4
    type [add 2, 2]
    press F4
  ]
  event-loop screen:&:screen, console:&:console, 3:&:environment
  screen-should-contain [
    .                                                                                 run (F4)           .
    .                                                                                                   .
    .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
    .                                                  0   edit          copy            delete         .
    .                                                  add 2, 2                                         .
    .                                                  4                                                .
    .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
    .                                                  1   edit          copy            delete         .
    .                                                  divide-with-remainder 11, 3                      .
    .                                                  3                                                .
    .                                                  2                                                .
    .                                                  ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━.
    .                                                                                                   .
  ]
  # delete second sandbox by clicking on left edge of 'delete' button
  assume-console<