https://github.com/akkartik/mu/blob/main/311decimal-int.subx
  1 # Helpers for decimal ints.
  2 
  3 # if slice doesn't contain a decimal number, return 0
  4 parse-decimal-int-from-slice:  # in: (addr slice) -> out/eax: int
  5     # . prologue
  6     55/push-ebp
  7     89/<- %ebp 4/r32/esp
  8     # . save registers
  9     51/push-ecx
 10     # ecx = in
 11     8b/-> *(ebp+8) 1/r32/ecx
 12     #
 13     (parse-decimal-int-helper *ecx *(ecx+4))  # => eax
 14 $parse-decimal-int-from-slice:end:
 15     # . restore registers
 16     59/pop-to-ecx
 17     # . epilogue
 18     89/<- %esp 5/r32/ebp
 19     5d/pop-to-ebp
 20     c3/return
 21 
 22 # if slice doesn't contain a decimal number, return 0
 23 parse-decimal-int:  # in: (addr array byte) -> result/eax: int
 24     # . prologue
 25     55/push-ebp
 26     89/<- %ebp 4/r32/esp
 27     # . save registers
 28     51/push-ecx
 29     52/push-edx
 30     # eax = in
 31     8b/-> *(ebp+8) 0/r32/eax
 32     # var start/ecx: (addr byte) = &in->data
 33     8d/copy-address *(eax+4) 1/r32/ecx
 34     # var end/edx: (addr byte) = &in->data[in->size]
 35     8b/-> *eax 2/r32/edx
 36     8d/copy-address *(eax+edx+4) 2/r32/edx
 37     #
 38     (parse-decimal-int-helper %ecx %edx)  # => eax
 39 $parse-decimal-int:end:
 40     # . restore registers
 41     5a/pop-to-edx
 42     59/pop-to-ecx
 43     # . epilogue
 44     89/<- %esp 5/r32/ebp
 45     5d/pop-to-ebp
 46     c3/return
 47 
 48 parse-decimal-int-from-stream:  # in: (addr stream byte) -> result/eax: int
 49     # . prologue
 50     55/push-ebp
 51     89/<- %ebp 4/r32/esp
 52     # . save registers
 53     51/push-ecx
 54     52/push-edx
 55     # eax = in
 56     8b/-> *(ebp+8) 0/r32/eax
 57     # var start/ecx: (addr byte) = &in->data[in->read]
 58     8b/-> *(eax+4) 1/r32/ecx
 59     8d/copy-address *(eax+ecx+0xc) 1/r32/ecx
 60     # var end/edx: (addr byte) = &in->data[in->write]
 61     8b/-> *eax 2/r32/edx
 62     8d/copy-address *(eax+edx+0xc) 2/r32/edx
 63     # trim a trailing newline
 64     {
 65       # speculatively trim
 66       4a/decrement-edx
 67       # if it's a newline, break
 68       8a/byte-> *edx 0/r32/eax
 69       25/and-eax-with 0xff/imm32
 70       3d/compare-eax-and 0xa/imm32/newline
 71       74/jump-if-= break/disp8
 72       # not a newline, so restore it
 73       42/increment-edx
 74     }
 75     #
 76     (parse-decimal-int-helper %ecx %edx)  # => eax
 77 $parse-decimal-int-from-stream:end:
 78     # . restore registers
 79     5a/pop-to-edx
 80     59/pop-to-ecx
 81     # . epilogue
 82     89/<- %esp 5/r32/ebp
 83     5d/pop-to-ebp
 84     c3/return
 85 
 86 parse-decimal-int-helper:  # start: (addr byte), end: (addr byte) -> result/eax: int
 87     # . prologue
 88     55/push-ebp
 89     89/<- %ebp 4/r32/esp
 90     # . save registers
 91     51/push-ecx
 92     52/push-edx
 93     53/push-ebx
 94     56/push-esi
 95     57/push-edi
 96     # var curr/esi: (addr byte) = start
 97     8b/-> *(ebp+8) 6/r32/esi
 98     # edi = end
 99     8b/-> *(ebp+0xc) 7/r32/edi
100     # var negate?/edx: boolean = false
101     ba/copy-to-edx 0/imm32/false
102     # if (*curr == '-') ++curr, negate = true
103     {
104 $parse-decimal-int-helper:negative:
105       b8/copy-to-eax 0/imm32
106       8a/copy-byte *esi 0/r32/AL
107       3d/compare-eax-and 0x2d/imm32/-
108       75/jump-if-!= break/disp8
109       # . ++curr
110       46/increment-esi
111       # . negate = true
112       ba/copy-to-edx  1/imm32/true
113     }
114     # spill negate?
115     52/push-edx
116     # var result/eax: int = 0
117     b8/copy-to-eax 0/imm32
118     # var digit/ecx: int = 0
119     b9/copy-to-ecx 0/imm32
120     # const TEN/ebx: int = 10
121     bb/copy-to-ebx 0xa/imm32
122     {
123 $parse-decimal-int-helper:loop:
124       # if (curr >= in->end) break
125       39/compare %esi 7/r32/edi
126       73/jump-if-addr>= break/disp8
127       # if !decimal-digit?(*curr) return 0
128       8a/copy-byte *esi 1/r32/CL
129       50/push-eax
130       (decimal-digit? %ecx)  # => eax
131       {
132         3d/compare-eax-and 0/imm32/false
133         75/jump-if-!= break/disp8
134         58/pop-to-eax
135         b8/copy-to-eax 0/imm32
136         eb/jump $parse-decimal-int-helper:negate/disp8
137       }
138       58/pop-to-eax
139       # digit = from-decimal-char(*curr)
140       81 5/subop/subtract %ecx 0x30/imm32/zero
141       # TODO: error checking
142       # result = result * 10 + digit
143       ba/copy-to-edx 0/imm32
144       f7 4/subop/multiply-into-edx-eax %ebx
145       # TODO: check edx for overflow
146       01/add %eax 1/r32/ecx
147       # ++curr
148       46/increment-esi
149       #
150       eb/jump loop/disp8
151     }
152 $parse-decimal-int-helper:negate:
153     # if (negate?) result = -result
154     5a/pop-to-edx
155     {
156       81 7/subop/compare %edx 0/imm32/false
157       74/jump-if-= break/disp8
158       f7 3/subop/negate %eax
159     }
160 $parse-decimal-int-helper:end:
161     # . restore registers
162     5f/pop-to-edi
163     5e/pop-to-esi
164     5b/pop-to-ebx
165     5a/pop-to-edx
166     59/pop-to-ecx
167     # . epilogue
168     89/<- %esp 5/r32/ebp
169     5d/pop-to-ebp
170     c3/return
171 
172 test-parse-decimal-int-from-slice-single-digit:
173     # . prologue
174     55/push-ebp
175     89/<- %ebp 4/r32/esp
176     # . save registers
177     50/push-eax
178     51/push-ecx
179     # (eax..ecx) = "3"
180     b8/copy-to-eax "3"/imm32
181     8b/-> *eax 1/r32/ecx
182     8d/copy-address *(eax+ecx+4) 1/r32/ecx
183     05/add-to-eax 4/imm32
184     # var slice/ecx: slice = {eax, ecx}
185     51/push-ecx
186     50/push-eax
187     89/<- %ecx 4/r32/esp
188     #
189     (parse-decimal-int-from-slice %ecx)  # => eax
190     (check-ints-equal %eax 3 "F - test-parse-decimal-int-from-slice-single-digit")
191 $test-parse-decimal-int-helper-single-digit:end:
192     # . restore registers
193     59/pop-to-ecx
194     58/pop-to-eax
195     # . epilogue
196     89/<- %esp 5/r32/ebp
197     5d/pop-to-ebp
198     c3/return
199 
200 test-parse-decimal-int-from-slice-multi-digit:
201     # . prologue
202     55/push-ebp
203     89/<- %ebp 4/r32/esp
204     # . save registers
205     50/push-eax
206     51/push-ecx
207     # (eax..ecx) = "34"
208     b8/copy-to-eax "34"/imm32
209     8b/-> *eax 1/r32/ecx
210     8d/copy-address *(eax+ecx+4) 1/r32/ecx
211     05/add-to-eax 4/imm32
212     # var slice/ecx: slice = {eax, ecx}
213     51/push-ecx
214     50/push-eax
215     89/<- %ecx 4/r32/esp
216     #
217     (parse-decimal-int-from-slice %ecx)  # => eax
218     (check-ints-equal %eax 0x22 "F - test-parse-decimal-int-from-slice-multi-digit")  # 34 in hex
219 $test-parse-decimal-int-helper-multi-digit:end:
220     # . restore registers
221     59/pop-to-ecx
222     58/pop-to-eax
223     # . epilogue
224     89/<- %esp 5/r32/ebp
225     5d/pop-to-ebp
226     c3/return
227 
228 test-parse-decimal-int-from-slice-zero:
229     # . prologue
230     55/push-ebp
231     89/<- %ebp 4/r32/esp
232     # . save registers
233     50/push-eax
234     51/push-ecx
235     # (eax..ecx) = "00"
236     b8/copy-to-eax "00"/imm32
237     8b/-> *eax 1/r32/ecx
238     8d/copy-address *(eax+ecx+4) 1/r32/ecx
239     05/add-to-eax 4/imm32
240     # var slice/ecx: slice = {eax, ecx}
241     51/push-ecx
242     50/push-eax
243     89/<- %ecx 4/r32/esp
244     #
245     (parse-decimal-int-from-slice %ecx)  # => eax
246     (check-ints-equal %eax 0 "F - test-parse-decimal-int-from-slice-zero")
247 $test-parse-decimal-int-helper-zero:end:
248     # . restore registers
249     59/pop-to-ecx
250     58/pop-to-eax
251     # . epilogue
252     89/<- %esp 5/r32/ebp
253     5d/pop-to-ebp
254     c3/return
255 
256 test-parse-decimal-int-from-slice-negative:
257     # . prologue
258     55/push-ebp
259     89/<- %ebp 4/r32/esp
260     # . save registers
261     50/push-eax
262     51/push-ecx
263     # (eax..ecx) = "-3"
264     b8/copy-to-eax "-3"/imm32
265     8b/-> *eax 1/r32/ecx
266     8d/copy-address *(eax+ecx+4) 1/r32/ecx
267     05/add-to-eax 4/imm32
268     # var slice/ecx: slice = {eax, ecx}
269     51/push-ecx
270     50/push-eax
271     89/<- %ecx 4/r32/esp
272     #
273     (parse-decimal-int-from-slice %ecx)  # => eax
274     (check-ints-equal %eax -3 "F - test-parse-decimal-int-from-slice-negative")
275 $test-parse-decimal-int-helper-negative:end:
276     # . restore registers
277     59/pop-to-ecx
278     58/pop-to-eax
279     # . epilogue
280     89/<- %esp 5/r32/ebp
281     5d/pop-to-ebp
282     c3/return
283 
284 test-parse-decimal-int-from-slice-multi-digit-negative:
285     # . prologue
286     55/push-ebp
287     89/<- %ebp 4/r32/esp
288     # . save registers
289     50/push-eax
290     51/push-ecx
291     # (eax..ecx) = "-32"
292     b8/copy-to-eax "-32"/imm32
293     8b/-> *eax 1/r32/ecx
294     8d/copy-address *(eax+ecx+4) 1/r32/ecx
295     05/add-to-eax 4/imm32
296     # var slice/ecx: slice = {eax, ecx}
297     51/push-ecx
298     50/push-eax
299     89/<- %ecx 4/r32/esp
300     #
301     (parse-decimal-int-from-slice %ecx)  # => eax
302     (check-ints-equal %eax -0x20 "F - test-parse-decimal-int-from-slice-multi-digit-negative")  # -32 in hex
303 $test-parse-decimal-int-helper-multi-digit-negative:end:
304     # . restore registers
305     59/pop-to-ecx
306     58/pop-to-eax
307     # . epilogue
308     89/<- %esp 5/r32/ebp
309     5d/pop-to-ebp
310     c3/return
311 
312 decimal-size:  # n: int -> result/eax: int
313     # pseudocode:
314     #   edi = 0
315     #   eax = n
316     #   if eax < 0
317     #     ++edi  # for '-'
318     #     negate eax
319     #   while true
320     #     edx = 0
321     #     eax, edx = eax/10, eax%10
322     #     ++edi
323     #     if (eax == 0) break
324     #   eax = edi
325     #
326     # . prologue
327     55/push-ebp
328     89/<- %ebp 4/r32/esp
329     # . save registers
330     51/push-ecx
331     52/push-edx
332     57/push-edi
333     # edi = 0
334     bf/copy-to-edi 0/imm32
335     # eax = n
336     8b/-> *(ebp+8) 0/r32/eax
337     # if (n < 0) negate n, increment edi
338     {
339       3d/compare-eax-with 0/imm32
340       7d/jump-if->= break/disp8
341       f7 3/subop/negate %eax
342       47/increment-edi
343     }
344     # const ten/ecx = 10
345     b9/copy-to-ecx  0xa/imm32
346     {
347       ba/copy-to-edx 0/imm32
348       f7 7/subop/idiv-edx-eax-by %ecx  # eax = edx:eax/10; edx = edx:eax%10
349       47/increment-edi
350       3d/compare-eax-and 0/imm32
351       75/jump-if-!= loop/disp8
352     }
353 $decimal-size:end:
354     89/<- %eax 7/r32/edi
355     # . restore registers
356     5f/pop-to-edi
357     5a/pop-to-edx
358     59/pop-to-ecx
359     # . epilogue
360     89/<- %esp 5/r32/ebp
361     5d/pop-to-ebp
362     c3/return
363 
364 test-decimal-size-of-zero:
365     # . prologue
366     55/push-ebp
367     89/<- %ebp 4/r32/esp
368     #
369     (decimal-size 0)  # => eax
370     (check-ints-equal %eax 1 "F - test-decimal-size-of-zero")
371 $test-decimal-size-of-zero:end:
372     # . epilogue
373     89/<- %esp 5/r32/ebp
374     5d/pop-to-ebp
375     c3/return
376 
377 test-decimal-size-single-digit:
378     # . prologue
379     55/push-ebp
380     89/<- %ebp 4/r32/esp
381     #
382     (decimal-size 4)  # => eax
383     (check-ints-equal %eax 1 "F - test-decimal-size-single-digit")
384 $test-decimal-size-single-digit:end:
385     # . epilogue
386     89/<- %esp 5/r32/ebp
387     5d/pop-to-ebp
388     c3/return
389 
390 test-decimal-size-multi-digit:
391     # . prologue
392     55/push-ebp
393     89/<- %ebp 4/r32/esp
394     #
395     (decimal-size 0xa)  # => eax
396     (check-ints-equal %eax 2 "F - test-decimal-size-multi-digit")
397 $test-decimal-size-multi-digit:end:
398     # . epilogue
399     89/<- %esp 5/r32/ebp
400     5d/pop-to-ebp
401     c3/return
402 
403 test-decimal-size-single-digit-negative:
404     # . prologue
405     55/push-ebp
406     89/<- %ebp 4/r32/esp
407     #
408     (decimal-size -4)  # => eax
409     (check-ints-equal %eax 2 "F - test-decimal-size-single-digit-negative")
410 $test-decimal-size-single-digit-negative:end:
411     # . epilogue
412     89/<- %esp 5/r32/ebp
413     5d/pop-to-ebp
414     c3/return
415 
416 test-decimal-size-multi-digit-negative:
417     # . prologue
418     55/push-ebp
419     89/<- %ebp 4/r32/esp
420     #
421     (decimal-size -0xa)  # => eax
422     (check-ints-equal %eax 3 "F - test-decimal-size-multi-digit-negative")
423 $test-decimal-size-multi-digit-negative:end:
424     # . epilogue
425     89/<- %esp 5/r32/ebp
426     5d/pop-to-ebp
427     c3/return
428 
429 _parse-array-of-decimal-ints:  # ad: (addr allocation-descriptor), s: (addr array byte), out: (addr handle array int)
430     # pseudocode
431     #   end = &s->data[s->size]
432     #   curr = s->data
433     #   size = 0
434     #   while true
435     #     if (curr >= end) break
436     #     curr = skip-chars-matching-in-slice(curr, end, ' ')
437     #     if (curr >= end) break
438     #     curr = skip-chars-not-matching-in-slice(curr, end, ' ')
439     #     ++size
440     #   allocate-array(ad, size*4, out)
441     #   var slice: slice = {s->data, 0}
442     #   curr = lookup(out)->data
443     #   while true
444     #     if (slice->start >= end) break
445     #     slice->start = skip-chars-matching-in-slice(slice->start, end, ' ')
446     #     if (slice->start >= end) break
447     #     slice->end = skip-chars-not-matching-in-slice(slice->start, end, ' ')
448     #     *curr = parse-hex-int-from-slice(slice)
449     #     curr += 4
450     #     slice->start = slice->end
451     #   return result
452     #
453     # . prologue
454     55/push-ebp
455     89/<- %ebp 4/r32/esp
456     # . save registers
457     50/push-eax
458     51/push-ecx
459     52/push-edx
460     53/push-ebx
461     56/push-esi
462     57/push-edi
463     # esi = s
464     8b/-> *(ebp+0xc) 6/r32/esi
465     # var curr/ecx: (addr byte) = s->data
466     8d/copy-address *(esi+4) 1/r32/ecx
467     # var end/edx: (addr byte) = &s->data[s->size]
468     # . edx = s->size
469     8b/-> *esi 2/r32/edx
470     # . edx += curr
471     01/add-to %edx 1/r32/ecx
472     # var size/ebx: int = 0
473     31/xor-with %ebx 3/r32/ebx
474 $_parse-array-of-decimal-ints:loop1:
475     # if (curr >= end) break
476     39/compare %ecx 2/r32/edx
477     73/jump-if-addr>= $_parse-array-of-decimal-ints:break1/disp8
478     # curr = skip-chars-matching-in-slice(curr, end, ' ')
479     (skip-chars-matching-in-slice %ecx %edx 0x20)  # => eax
480     89/<- %ecx 0/r32/eax
481     # if (curr >= end) break
482     39/compare %ecx 2/r32/edx
483     73/jump-if-addr>= $_parse-array-of-decimal-ints:break1/disp8
484     # curr = skip-chars-not-matching-in-slice(curr, end, ' ')
485     (skip-chars-not-matching-in-slice %ecx %edx 0x20)  # => eax
486     89/<- %ecx 0/r32/eax
487     # size += 4
488     81 0/subop/add %ebx 4/imm32
489     eb/jump $_parse-array-of-decimal-ints:loop1/disp8
490 $_parse-array-of-decimal-ints:break1:
491     (allocate-array *(ebp+8) %ebx *(ebp+0x10))
492 $_parse-array-of-decimal-ints:pass2:
493     # var slice/edi: slice = {s->data, 0}
494     68/push 0/imm32/end
495     8d/copy-address *(esi+4) 7/r32/edi
496     57/push-edi
497     89/<- %edi 4/r32/esp
498     # curr = lookup(out)->data
499     8b/-> *(ebp+0x10) 0/r32/eax
500     (lookup *eax *(eax+4))  # => eax
501     8d/copy-address *(eax+4) 1/r32/ecx
502 $_parse-array-of-decimal-ints:loop2:
503     # if (slice->start >= end) break
504     39/compare *edi 2/r32/edx
505     73/jump-if-addr>= $_parse-array-of-decimal-ints:end/disp8
506     # slice->start = skip-chars-matching-in-slice(slice->start, end, ' ')
507     (skip-chars-matching-in-slice *edi %edx 0x20)  # => eax
508     89/<- *edi 0/r32/eax
509     # if (slice->start >= end) break
510     39/compare *edi 2/r32/edx
511     73/jump-if-addr>= $_parse-array-of-decimal-ints:end/disp8
512     # slice->end = skip-chars-not-matching-in-slice(slice->start, end, ' ')
513     (skip-chars-not-matching-in-slice *edi %edx 0x20)  # => eax
514     89/<- *(edi+4) 0/r32/eax
515     # *curr = parse-hex-int-from-slice(slice)
516     (parse-decimal-int-from-slice %edi)
517     89/<- *ecx 0/r32/eax
518     # curr += 4
519     81 0/subop/add %ecx 4/imm32
520     # slice->start = slice->end
521     8b/-> *(edi+4) 0/r32/eax
522     89/<- *edi 0/r32/eax
523     eb/jump $_parse-array-of-decimal-ints:loop2/disp8
524 $_parse-array-of-decimal-ints:end:
525     # . reclaim locals
526     81 0/subop/add %esp 8/imm32
527     # . restore registers
528     5f/pop-to-edi
529     5e/pop-to-esi
530     5b/pop-to-ebx
531     5a/pop-to-edx
532     59/pop-to-ecx
533     58/pop-to-eax
534     # . epilogue
535     89/<- %esp 5/r32/ebp
536     5d/pop-to-ebp
537     c3/return
538 
539 test-parse-array-of-decimal-ints:
540     # . prologue
541     55/push-ebp
542     89/<- %ebp 4/r32/esp
543     # var h/esi: (handle array int)
544     68/push 0/imm32
545     68/push 0/imm32
546     89/<- %esi 4/r32/esp
547     # var ecx: (array int) = [1, 2, 3]
548     68/push 3/imm32
549     68/push 2/imm32
550     68/push 1/imm32
551     68/push 0xc/imm32/size
552     89/<- %ecx 4/r32/esp
553     #
554     (_parse-array-of-decimal-ints Heap "1 2 3" %esi)
555     (lookup *esi *(esi+4))  # => eax
556     (array-equal? %ecx %eax)  # => eax
557     (check-ints-equal %eax 1 "F - test-parse-array-of-decimal-ints")
558     # . epilogue
559     89/<- %esp 5/r32/ebp
560     5d/pop-to-ebp
561     c3/return
562 
563 test-parse-array-of-decimal-ints-empty:
564     # - empty string = empty array
565     # . prologue
566     55/push-ebp
567     89/<- %ebp 4/r32/esp
568     # var h/esi: handle
569     68/push 0/imm32
570     68/push 0/imm32
571     89/<- %esi 4/r32/esp
572     #
573     (_parse-array-of-decimal-ints Heap "" %esi)
574     (lookup *esi *(esi+4))  # => eax
575     (check-ints-equal *eax 0 "F - test-parse-array-of-decimal-ints-empty")
576     # . epilogue
577     89/<- %esp 5/r32/ebp
578     5d/pop-to-ebp
579     c3/return
580 
581 test-parse-array-of-decimal-ints-just-whitespace:
582     # - just whitespace = empty array
583     # . prologue
584     55/push-ebp
585     89/<- %ebp 4/r32/esp
586     # var h/esi: handle
587     68/push 0/imm32
588     68/push 0/imm32
589     89/<- %esi 4/r32/esp
590     #
591     (_parse-array-of-decimal-ints Heap Space %esi)
592     (lookup *esi *(esi+4))  # => eax
593     (check-ints-equal *eax 0 "F - test-parse-array-of-decimal-ints-just-whitespace")
594     # . epilogue
595     89/<- %esp 5/r32/ebp
596     5d/pop-to-ebp
597     c3/return
598 
599 test-parse-array-of-decimal-ints-extra-whitespace:
600     # . prologue
601     55/push-ebp
602     89/<- %ebp 4/r32/esp
603     # var h/esi: handle
604     68/push 0/imm32
605     68/push 0/imm32
606     89/<- %esi 4/r32/esp
607     # var ecx: (array int) = [1, 2, 3]
608     68/push 3/imm32
609     68/push 2/imm32
610     68/push 1/imm32
611     68/push 0xc/imm32/size
612     89/<- %ecx 4/r32/esp
613     #
614     (_parse-array-of-decimal-ints Heap " 1 2  3  " %esi)
615     (lookup *esi *(esi+4))  # => eax
616     (array-equal? %ecx %eax)  # => eax
617     (check-ints-equal %eax 1 "F - test-parse-array-of-decimal-ints-extra-whitespace")
618     # . epilogue
619     89/<- %esp 5/r32/ebp
620     5d/pop-to-ebp
621     c3/return
622 
623 parse-array-of-decimal-ints:  # s: (addr array byte), out: (addr handle array int)
624     # . prologue
625     55/push-ebp
626     89/<- %ebp 4/r32/esp
627     #
628     (_parse-array-of-decimal-ints Heap *(ebp+8) *(ebp+0xc))
629 $parse-array-of-decimal-ints:end:
630     # . epilogue
631     89/<- %esp 5/r32/ebp
632     5d/pop-to-ebp
633     c3/return