about summary refs log tree commit diff stats
path: root/baremetal/126write-int-decimal.subx
blob: 9f14824853baea5f5f4c7f46019a82c0e81cf626 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 */
# Test some primitives for text-mode.
#
# To run:
#   $ ./translate_mu apps/tui.mu
#   $ ./a.elf

fn main -> _/ebx: int {
  var nrows/eax: int <- copy 0
  var ncols/ecx: int <- copy 0
  nrows, ncols <- screen-size 0
  enable-screen-grid-mode
  move-cursor 0, 5, 0x22
  start-color 0, 1, 0x7a
  start-blinking 0
  print-string 0, "Hello world!"
  reset-formatting 0
  move-cursor 0, 6, 0x22
  print-string 0, "tty dimensions: "
  print-int32-hex 0, nrows
  print-string 0, " rows, "
  print-int32-hex 0, ncols
  print-string 0, " rows\n"

  print-string 0, "press a key to see its code: "
  enable-keyboard-immediate-mode
  var x/eax: grapheme <- read-key-from-real-keyboard
  enable-keyboard-type-mode
  enable-screen-type-mode
  print-string 0, "You pressed "
  var x-int/eax: int <- copy x
  print-int32-hex 0, x-int
  print
# Helper to print an int32 in decimal.

== code
#   instruction                     effective address                                                   register    displacement    immediate
# . op          subop               mod             rm32          base        index         scale       r32
# . 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

write-int32-decimal:  # out: (addr stream byte), n: int
    # works by generating characters from lowest to highest and pushing them
    # to the stack, before popping them one by one into the stream
    #
    # pseudocode:
    #   push sentinel
    #   eax = abs(n)
    #   while true
    #     sign-extend eax into edx
    #     eax, edx = eax/10, eax%10
    #     edx += '0'
    #     push edx
    #     if (eax == 0) break
    #   if n < 0
    #     push '-'
    #   w = out->write
    #   curr = &out->data[out->write]
    #   max = &out->data[out->size]
    #   while true
    #     pop into eax
    #     if (eax == sentinel) break
    #     if (curr >= max) abort
    #     *curr = AL
    #     ++curr
    #     ++w
    #   out->write = w
    # (based on K&R itoa: https://en.wikibooks.org/wiki/C_Programming/stdlib.h/itoa)
    # (this pseudocode contains registers because operations like division
    # require specific registers in x86)
    #
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    57/push-edi
    # const ten/ecx = 10
    b9/copy-to-ecx  0xa/imm32
    # push sentinel
    68/push  0/imm32/sentinel
    # var eax: int = abs(n)
    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
    3d/compare-eax-with  0/imm32
    7d/jump-if->=  $write-int32-decimal:read-loop/disp8
$write-int32-decimal:negative:
    f7          3/subop/negate      3/mod/direct    0/rm32/eax    .           .             .           .           .               .                 # negate eax
$write-int32-decimal:read-loop:
    # eax, edx = eax / 10, eax % 10
    99/sign-extend-eax-into-edx
    f7          7/subop/idiv        3/mod/direct    1/rm32/ecx    .           .             .           .           .               .                 # divide edx:eax by ecx, storing quotient in eax and remainder in edx
    # edx += '0'
    81          0/subop/add         3/mod/direct    2/rm32/edx    .           .             .           .           .               0x30/imm32        # add to edx
    # push edx
    52/push-edx
    # if (eax == 0) break
    3d/compare-eax-and  0/imm32
    7f/jump-if->  $write-int32-decimal:read-loop/disp8
$write-int32-decimal:read-break:
    # if (n < 0) push('-')
    81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       0/imm32           # compare *(ebp+12)
    7d/jump-if->=  $write-int32-decimal:write/disp8
$write-int32-decimal:push-negative:
    68/push  0x2d/imm32/-
$write-int32-decimal:write:
    # edi = out
    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   8/disp8         .                 # copy *(ebp+8) to edi
    # var w/edx: int = out->write
    8b/copy                         0/mod/indirect  7/rm32/edi    .           .             .           2/r32/edx   .               .                 # copy *edi to edx
    # var curr/ecx: (addr byte) = &out->data[out->write]
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/edi  2/index/edx   .           1/r32/ecx   0xc/disp8       .                 # copy ebx+edx+12 to ecx
    # var max/ebx: (addr byte) = &out->data[out->size]
    8b/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           3/r32/ebx   8/disp8         .                 # copy *(edi+8) to ebx
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    7/base/edi  3/index/ebx   .           3/r32/ebx   0xc/disp8       .                 # copy edi+ebx+12 to ebx
$write-int32-decimal:write-loop:
    # pop into eax
    58/pop-to-eax
    # if (eax == sentinel) break
    3d/compare-eax-and  0/imm32/sentinel
    74/jump-if-=  $write-int32-decimal:write-break/disp8
    # if (curr >= max) abort
    39/compare                      3/mod/direct    1/rm32/ecx    .           .             .           3/r32/ebx   .               .                 # compare ecx with ebx
    73/jump-if-addr>=  $write-int32-decimal:abort/disp8
$write-int32-decimal:write-char:
    # *curr = AL
    88/copy-byte                    0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/AL    .               .                 # copy AL to byte at *ecx
    # ++curr
    41/increment-ecx
    # ++w
    42/increment-edx
    eb/jump  $write-int32-decimal:write-loop/disp8
$write-int32-decimal:write-break:
    # out->write = w
    89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           2/r32/edx   .               .                 # copy edx to *edi
$write-int32-decimal:end:
    # . restore registers
    5f/pop-to-edi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

$write-int32-decimal:abort:
    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "write-int32-decimal: stream out of space" 3 0)  # 3=cyan
    {
      eb/jump loop/disp8
    }
    # never gets here

test-write-int32-decimal:
    # - check that a single-digit number converts correctly
    # setup
    # . clear-stream(_test-stream)
    # . . push args
    68/push  _test-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # write-int32-decimal(_test-stream, 9)
    # . . push args
    68/push  9/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  write-int32-decimal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # check-stream-equal(_test-stream, "9", msg)
    # . . push args
    68/push  "F - test-write-int32-decimal"/imm32
    68/push  "9"/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  check-stream-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . end
    c3/return

test-write-int32-decimal-zero:
    # - check that 0 converts correctly
    # setup
    # . clear-stream(_test-stream)
    # . . push args
    68/push  _test-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # write-int32-decimal(_test-stream, 0)
    # . . push args
    68/push  0/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  write-int32-decimal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # check-stream-equal(_test-stream, "0", msg)
    # . . push args
    68/push  "F - test-write-int32-decimal-zero"/imm32
    68/push  "0"/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  check-stream-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . end
    c3/return

test-write-int32-decimal-multiple-digits:
    # - check that a multi-digit number converts correctly
    # setup
    # . clear-stream(_test-stream)
    # . . push args
    68/push  _test-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # write-int32-decimal(_test-stream, 10)
    # . . push args
    68/push  0xa/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  write-int32-decimal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # check-stream-equal(_test-stream, "10", msg)
    # . . push args
    68/push  "F - test-write-int32-decimal-multiple-digits"/imm32
    68/push  "10"/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  check-stream-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . end
    c3/return

test-write-int32-decimal-negative:
    # - check that a negative single-digit number converts correctly
    # setup
    # . clear-stream(_test-stream)
    # . . push args
    68/push  _test-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # write-int32-decimal(_test-stream, -9)
    # . . push args
    68/push  -9/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  write-int32-decimal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # dump _test-stream {{{
#?     # . write(2/stderr, "^")
#?     # . . push args
#?     68/push  "^"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write-stream(2/stderr, _test-stream)
#?     # . . push args
#?     68/push  _test-stream/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write-stream/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # . write(2/stderr, "$\n")
#?     # . . push args
#?     68/push  "$\n"/imm32
#?     68/push  2/imm32/stderr
#?     # . . call
#?     e8/call  write/disp32
#?     # . . discard args
#?     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
#?     # }}}
    # check-stream-equal(_test-stream, "-9", msg)
    # . . push args
    68/push  "F - test-write-int32-decimal-negative"/imm32
    68/push  "-9"/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  check-stream-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . end
    c3/return

test-write-int32-decimal-negative-multiple-digits:
    # - check that a multi-digit number converts correctly
    # setup
    # . clear-stream(_test-stream)
    # . . push args
    68/push  _test-stream/imm32
    # . . call
    e8/call  clear-stream/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # write-int32-decimal(_test-stream, -10)
    # . . push args
    68/push  -0xa/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  write-int32-decimal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # check-stream-equal(_test-stream, "-10", msg)
    # . . push args
    68/push  "F - test-write-int32-decimal-negative-multiple-digits"/imm32
    68/push  "-10"/imm32
    68/push  _test-stream/imm32
    # . . call
    e8/call  check-stream-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . end
    c3/return

is-decimal-digit?:  # c: grapheme -> result/eax: boolean
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # . save registers
    51/push-ecx
    # ecx = c
    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
    # result = false
    b8/copy-to-eax  0/imm32/false
    # return false if c < '0'
    81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x30/imm32        # compare ecx
    7c/jump-if-<  $is-decimal-digit?:end/disp8
    # return (c <= '9')
    81          7/subop/compare     3/mod/direct    1/rm32/ecx    .           .             .           .           .               0x39/imm32        # compare ecx
    7f/jump-if->  $is-decimal-digit?:end/disp8
$is-decimal-digit?:true:
    b8/copy-to-eax  1/imm32/true
$is-decimal-digit?:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

test-is-decimal-digit-below-0:
    # eax = is-decimal-digit?(0x2f)
    # . . push args
    68/push  0x2f/imm32
    # . . call
    e8/call  is-decimal-digit?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # check-ints-equal(eax, 0, msg)
    # . . push args
    68/push  "F - test-is-decimal-digit-below-0"/imm32
    68/push  0/imm32/false
    50/push-eax
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    c3/return

test-is-decimal-digit-0-to-9:
    # eax = is-decimal-digit?(0x30)
    # . . push args
    68/push  0x30/imm32
    # . . call
    e8/call  is-decimal-digit?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # check-ints-equal(eax, 1, msg)
    # . . push args
    68/push  "F - test-is-decimal-digit-at-0"/imm32
    68/push  1/imm32/true
    50/push-eax
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # eax = is-decimal-digit?(0x39)
    # . . push args
    68/push  0x39/imm32
    # . . call
    e8/call  is-decimal-digit?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # check-ints-equal(eax, 1, msg)
    # . . push args
    68/push  "F - test-is-decimal-digit-at-9"/imm32
    68/push  1/imm32/true
    50/push-eax
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    c3/return

test-is-decimal-digit-above-9:
    # eax = is-decimal-digit?(0x3a)
    # . . push args
    68/push  0x3a/imm32
    # . . call
    e8/call  is-decimal-digit?/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # check-ints-equal(eax, 0, msg)
    # . . push args
    68/push  "F - test-is-decimal-digit-above-9"/imm32
    68/push  0/imm32/false
    50/push-eax
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    c3/return

to-decimal-digit:  # in: grapheme -> out/eax: int
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # eax = in
    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   8/disp8         .                 # copy *(ebp+8) to eax
$to-decimal-digit:check0:
    # if (eax < '0') goto abort
    3d/compare-eax-with  0x30/imm32/0
    7c/jump-if-<  $to-decimal-digit:abort/disp8
$to-decimal-digit:check1:
    # if (eax > '9') goto abort
    3d/compare-eax-with  0x39/imm32/f
    7f/jump-if->  $to-decimal-digit:abort/disp8
$to-decimal-digit:digit:
    # return eax - '0'
    2d/subtract-from-eax  0x30/imm32/0
$to-decimal-digit:end:
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

$to-decimal-digit:abort:
    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "to-decimal-digit: not a digit character" 3 0)  # 3=cyan
    {
      eb/jump loop/disp8
    }
    # never gets here

# . . vim:nowrap:textwidth=0