about summary refs log tree commit diff stats
path: root/311decimal-int.subx
blob: 62ec51d65c9daced454004a13df85a69d2d3e438 (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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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 */
#!/bin/sh

# init variables
version="v2022.11.03"
ENDPOINT="https://0.vern.cc"
flag_options=":hvcfe:s:"
long_flag_options="help,version,color,file,extension:,server:"
flag_version=0
flag_help=0
flag_file=0
flag_colors=0
flag_ext=0
data=""
EXT=""

# help message available via func
show_help() {
  cat > /dev/stdout << END
pb [options] filename
or
(command-with-stdout) | pb

Uploads a file or data to the tilde 0x0 paste bin

OPTIONAL FLAGS:
  -h | --help)                    Show this help
  -v | --version)                 Show current version number
  -f | --file)                    Explicitly interpret stdin as filename
  -c | --color)                   Pretty color output
  -s | --server server_address)   Use alternative pastebin server address
  -e | --extension bin_extension) Specify file extension used in the upload
END
}

show_usage() {
  cat > /dev/stdout << END
usage: pb [-hfvcux] [-s server_address] filename
END
}

# helper for program exit, supports error codes and messages
die () {
  msg="$1"
  code="$2"
  # exit code defaults to 1
  if printf "%s" "${code}" | grep -q '^[0-9]+$'; then
    code=1
  fi
  # output message to stdout or stderr based on code
  if [ -n "${msg}" ]; then
    if [ "${code}" -eq 0 ]; then
      printf "%s\\n" "${msg}"
    else
      printf "%s%s%s\\n" "$ERROR" "${msg}" "$RESET" >&2
    fi
  fi
  exit "${code}"
}

# attempt to parse options or die
if ! PARSED_ARGUMENTS=$(getopt -a -n pb -o ${flag_options} --long ${long_flag_options} -- "$@"); then
  printf "pb: unknown option\\n"
  show_usage
  exit 2
fi

# For debugging: echo "PARSED_ARGUMENTS is $PARSED_ARGUMENTS"
eval set -- "$PARSED_ARGUMENTS"
while :
do
  case "$1" in
    -h | --help)      flag_help=1                  ; shift   ;;
    -v | --version)   flag_version=1               ; shift   ;;
    -c | --color)     flag_color=1                 ; shift   ;;
    -f | --file)      flag_file=1                  ; shift   ;;
    -e | --extension) flag_ext=1;    EXT="$2"      <
# Helpers for decimal ints.

# if slice doesn't contain a decimal number, return 0
parse-decimal-int-from-slice:  # in: (addr slice) -> out/eax: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    # ecx = in
    8b/-> *(ebp+8) 1/r32/ecx
    #
    (parse-decimal-int-helper *ecx *(ecx+4))  # => eax
$parse-decimal-int-from-slice:end:
    # . restore registers
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# if slice doesn't contain a decimal number, return 0
parse-decimal-int:  # in: (addr array byte) -> result/eax: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    # eax = in
    8b/-> *(ebp+8) 0/r32/eax
    # var start/ecx: (addr byte) = &in->data
    8d/copy-address *(eax+4) 1/r32/ecx
    # var end/edx: (addr byte) = &in->data[in->size]
    8b/-> *eax 2/r32/edx
    8d/copy-address *(eax+edx+4) 2/r32/edx
    #
    (parse-decimal-int-helper %ecx %edx)  # => eax
$parse-decimal-int:end:
    # . restore registers
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

parse-decimal-int-from-stream:  # in: (addr stream byte) -> result/eax: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    # eax = in
    8b/-> *(ebp+8) 0/r32/eax
    # var start/ecx: (addr byte) = &in->data[in->read]
    8b/-> *(eax+4) 1/r32/ecx
    8d/copy-address *(eax+ecx+0xc) 1/r32/ecx
    # var end/edx: (addr byte) = &in->data[in->write]
    8b/-> *eax 2/r32/edx
    8d/copy-address *(eax+edx+0xc) 2/r32/edx
    #
    (parse-decimal-int-helper %ecx %edx)  # => eax
$parse-decimal-int-from-stream:end:
    # . restore registers
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

parse-decimal-int-helper:  # start: (addr byte), end: (addr byte) -> result/eax: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    53/push-ebx
    56/push-esi
    57/push-edi
    # var curr/esi: (addr byte) = start
    8b/-> *(ebp+8) 6/r32/esi
    # edi = end
    8b/-> *(ebp+0xc) 7/r32/edi
    # var negate?/edx: boolean = false
    ba/copy-to-edx 0/imm32/false
    # if (*curr == '-') ++curr, negate = true
    {
$parse-decimal-int-helper:negative:
      b8/copy-to-eax 0/imm32
      8a/copy-byte *esi 0/r32/AL
      3d/compare-eax-and 0x2d/imm32/-
      75/jump-if-!= break/disp8
      # . ++curr
      46/increment-esi
      # . negate = true
      ba/copy-to-edx  1/imm32/true
    }
    # spill negate?
    52/push-edx
    # var result/eax: int = 0
    b8/copy-to-eax 0/imm32
    # var digit/ecx: int = 0
    b9/copy-to-ecx 0/imm32
    # const TEN/ebx: int = 10
    bb/copy-to-ebx 0xa/imm32
    {
$parse-decimal-int-helper:loop:
      # if (curr >= in->end) break
      39/compare %esi 7/r32/edi
      73/jump-if-addr>= break/disp8
      # if !is-decimal-digit?(*curr) return 0
      8a/copy-byte *esi 1/r32/CL
      50/push-eax
      (is-decimal-digit? %ecx)  # => eax
      {
        3d/compare-eax-and 0/imm32/false
        75/jump-if-!= break/disp8
        58/pop-to-eax
        b8/copy-to-eax 0/imm32
        eb/jump $parse-decimal-int-helper:negate/disp8
      }
      58/pop-to-eax
      # digit = from-decimal-char(*curr)
      81 5/subop/subtract %ecx 0x30/imm32/zero
      # TODO: error checking
      # result = result * 10 + digit
      ba/copy-to-edx 0/imm32
      f7 4/subop/multiply-into-edx-eax %ebx
      # TODO: check edx for overflow
      01/add %eax 1/r32/ecx
      # ++curr
      46/increment-esi
      #
      eb/jump loop/disp8
    }
$parse-decimal-int-helper:negate:
    # if (negate?) result = -result
    5a/pop-to-edx
    {
      81 7/subop/compare %edx 0/imm32/false
      74/jump-if-= break/disp8
      f7 3/subop/negate %eax
    }
$parse-decimal-int-helper:end:
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-decimal-int-from-slice-single-digit:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    # (eax..ecx) = "3"
    b8/copy-to-eax "3"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (parse-decimal-int-from-slice %ecx)  # => eax
    (check-ints-equal %eax 3 "F - test-parse-decimal-int-from-slice-single-digit")
$test-parse-decimal-int-helper-single-digit:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-decimal-int-from-slice-multi-digit:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    # (eax..ecx) = "34"
    b8/copy-to-eax "34"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (parse-decimal-int-from-slice %ecx)  # => eax
    (check-ints-equal %eax 0x22 "F - test-parse-decimal-int-from-slice-multi-digit")  # 34 in hex
$test-parse-decimal-int-helper-multi-digit:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-decimal-int-from-slice-zero:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    # (eax..ecx) = "00"
    b8/copy-to-eax "00"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (parse-decimal-int-from-slice %ecx)  # => eax
    (check-ints-equal %eax 0 "F - test-parse-decimal-int-from-slice-zero")
$test-parse-decimal-int-helper-zero:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-decimal-int-from-slice-negative:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    # (eax..ecx) = "-3"
    b8/copy-to-eax "-3"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (parse-decimal-int-from-slice %ecx)  # => eax
    (check-ints-equal %eax -3 "F - test-parse-decimal-int-from-slice-negative")
$test-parse-decimal-int-helper-negative:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-decimal-int-from-slice-multi-digit-negative:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    # (eax..ecx) = "-32"
    b8/copy-to-eax "-32"/imm32
    8b/-> *eax 1/r32/ecx
    8d/copy-address *(eax+ecx+4) 1/r32/ecx
    05/add-to-eax 4/imm32
    # var slice/ecx: slice = {eax, ecx}
    51/push-ecx
    50/push-eax
    89/<- %ecx 4/r32/esp
    #
    (parse-decimal-int-from-slice %ecx)  # => eax
    (check-ints-equal %eax -0x20 "F - test-parse-decimal-int-from-slice-multi-digit-negative")  # -32 in hex
$test-parse-decimal-int-helper-multi-digit-negative:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

decimal-size:  # n: int -> result/eax: int
    # pseudocode:
    #   edi = 0
    #   eax = n
    #   if eax < 0
    #     ++edi  # for '-'
    #     negate eax
    #   while true
    #     edx = 0
    #     eax, edx = eax/10, eax%10
    #     ++edi
    #     if (eax == 0) break
    #   eax = edi
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    51/push-ecx
    52/push-edx
    57/push-edi
    # edi = 0
    bf/copy-to-edi 0/imm32
    # eax = n
    8b/-> *(ebp+8) 0/r32/eax
    # if (n < 0) negate n, increment edi
    {
      3d/compare-eax-with 0/imm32
      7d/jump-if->= break/disp8
      f7 3/subop/negate %eax
      47/increment-edi
    }
    # const ten/ecx = 10
    b9/copy-to-ecx  0xa/imm32
    {
      ba/copy-to-edx 0/imm32
      f7 7/subop/idiv-edx-eax-by %ecx  # eax = edx:eax/10; edx = edx:eax%10
      47/increment-edi
      3d/compare-eax-and 0/imm32
      75/jump-if-!= loop/disp8
    }
$decimal-size:end:
    89/<- %eax 7/r32/edi
    # . restore registers
    5f/pop-to-edi
    5a/pop-to-edx
    59/pop-to-ecx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-decimal-size-of-zero:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
    (decimal-size 0)  # => eax
    (check-ints-equal %eax 1 "F - test-decimal-size-of-zero")
$test-decimal-size-of-zero:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-decimal-size-single-digit:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
    (decimal-size 4)  # => eax
    (check-ints-equal %eax 1 "F - test-decimal-size-single-digit")
$test-decimal-size-single-digit:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-decimal-size-multi-digit:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
    (decimal-size 0xa)  # => eax
    (check-ints-equal %eax 2 "F - test-decimal-size-multi-digit")
$test-decimal-size-multi-digit:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-decimal-size-single-digit-negative:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
    (decimal-size -4)  # => eax
    (check-ints-equal %eax 2 "F - test-decimal-size-single-digit-negative")
$test-decimal-size-single-digit-negative:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-decimal-size-multi-digit-negative:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
    (decimal-size -0xa)  # => eax
    (check-ints-equal %eax 3 "F - test-decimal-size-multi-digit-negative")
$test-decimal-size-multi-digit-negative:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

_parse-array-of-decimal-ints:  # ad: (addr allocation-descriptor), s: (addr array byte), out: (addr handle array int)
    # pseudocode
    #   end = &s->data[s->size]
    #   curr = s->data
    #   size = 0
    #   while true
    #     if (curr >= end) break
    #     curr = skip-chars-matching-in-slice(curr, end, ' ')
    #     if (curr >= end) break
    #     curr = skip-chars-not-matching-in-slice(curr, end, ' ')
    #     ++size
    #   allocate-array(ad, size*4, out)
    #   var slice: slice = {s->data, 0}
    #   curr = lookup(out)->data
    #   while true
    #     if (slice->start >= end) break
    #     slice->start = skip-chars-matching-in-slice(slice->start, end, ' ')
    #     if (slice->start >= end) break
    #     slice->end = skip-chars-not-matching-in-slice(slice->start, end, ' ')
    #     *curr = parse-hex-int-from-slice(slice)
    #     curr += 4
    #     slice->start = slice->end
    #   return result
    #
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    56/push-esi
    57/push-edi
    # esi = s
    8b/-> *(ebp+0xc) 6/r32/esi
    # var curr/ecx: (addr byte) = s->data
    8d/copy-address *(esi+4) 1/r32/ecx
    # var end/edx: (addr byte) = &s->data[s->size]
    # . edx = s->size
    8b/-> *esi 2/r32/edx
    # . edx += curr
    01/add-to %edx 1/r32/ecx
    # var size/ebx: int = 0
    31/xor-with %ebx 3/r32/ebx
$_parse-array-of-decimal-ints:loop1:
    # if (curr >= end) break
    39/compare %ecx 2/r32/edx
    73/jump-if-addr>= $_parse-array-of-decimal-ints:break1/disp8
    # curr = skip-chars-matching-in-slice(curr, end, ' ')
    (skip-chars-matching-in-slice %ecx %edx 0x20)  # => eax
    89/<- %ecx 0/r32/eax
    # if (curr >= end) break
    39/compare %ecx 2/r32/edx
    73/jump-if-addr>= $_parse-array-of-decimal-ints:break1/disp8
    # curr = skip-chars-not-matching-in-slice(curr, end, ' ')
    (skip-chars-not-matching-in-slice %ecx %edx 0x20)  # => eax
    89/<- %ecx 0/r32/eax
    # size += 4
    81 0/subop/add %ebx 4/imm32
    eb/jump $_parse-array-of-decimal-ints:loop1/disp8
$_parse-array-of-decimal-ints:break1:
    (allocate-array *(ebp+8) %ebx *(ebp+0x10))
$_parse-array-of-decimal-ints:pass2:
    # var slice/edi: slice = {s->data, 0}
    68/push 0/imm32/end
    8d/copy-address *(esi+4) 7/r32/edi
    57/push-edi
    89/<- %edi 4/r32/esp
    # curr = lookup(out)->data
    8b/-> *(ebp+0x10) 0/r32/eax
    (lookup *eax *(eax+4))  # => eax
    8d/copy-address *(eax+4) 1/r32/ecx
$_parse-array-of-decimal-ints:loop2:
    # if (slice->start >= end) break
    39/compare *edi 2/r32/edx
    73/jump-if-addr>= $_parse-array-of-decimal-ints:end/disp8
    # slice->start = skip-chars-matching-in-slice(slice->start, end, ' ')
    (skip-chars-matching-in-slice *edi %edx 0x20)  # => eax
    89/<- *edi 0/r32/eax
    # if (slice->start >= end) break
    39/compare *edi 2/r32/edx
    73/jump-if-addr>= $_parse-array-of-decimal-ints:end/disp8
    # slice->end = skip-chars-not-matching-in-slice(slice->start, end, ' ')
    (skip-chars-not-matching-in-slice *edi %edx 0x20)  # => eax
    89/<- *(edi+4) 0/r32/eax
    # *curr = parse-hex-int-from-slice(slice)
    (parse-decimal-int-from-slice %edi)
    89/<- *ecx 0/r32/eax
    # curr += 4
    81 0/subop/add %ecx 4/imm32
    # slice->start = slice->end
    8b/-> *(edi+4) 0/r32/eax
    89/<- *edi 0/r32/eax
    eb/jump $_parse-array-of-decimal-ints:loop2/disp8
$_parse-array-of-decimal-ints:end:
    # . reclaim locals
    81 0/subop/add %esp 8/imm32
    # . restore registers
    5f/pop-to-edi
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-array-of-decimal-ints:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # var h/esi: (handle array int)
    68/push 0/imm32
    68/push 0/imm32
    89/<- %esi 4/r32/esp
    # var ecx: (array int) = [1, 2, 3]
    68/push 3/imm32
    68/push 2/imm32
    68/push 1/imm32
    68/push 0xc/imm32/size
    89/<- %ecx 4/r32/esp
    #
    (_parse-array-of-decimal-ints Heap "1 2 3" %esi)
    (lookup *esi *(esi+4))  # => eax
    (array-equal? %ecx %eax)  # => eax
    (check-ints-equal %eax 1 "F - test-parse-array-of-decimal-ints")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-array-of-decimal-ints-empty:
    # - empty string = empty array
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # var h/esi: handle
    68/push 0/imm32
    68/push 0/imm32
    89/<- %esi 4/r32/esp
    #
    (_parse-array-of-decimal-ints Heap "" %esi)
    (lookup *esi *(esi+4))  # => eax
    (check-ints-equal *eax 0 "F - test-parse-array-of-decimal-ints-empty")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-array-of-decimal-ints-just-whitespace:
    # - just whitespace = empty array
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # var h/esi: handle
    68/push 0/imm32
    68/push 0/imm32
    89/<- %esi 4/r32/esp
    #
    (_parse-array-of-decimal-ints Heap Space %esi)
    (lookup *esi *(esi+4))  # => eax
    (check-ints-equal *eax 0 "F - test-parse-array-of-decimal-ints-just-whitespace")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

test-parse-array-of-decimal-ints-extra-whitespace:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # var h/esi: handle
    68/push 0/imm32
    68/push 0/imm32
    89/<- %esi 4/r32/esp
    # var ecx: (array int) = [1, 2, 3]
    68/push 3/imm32
    68/push 2/imm32
    68/push 1/imm32
    68/push 0xc/imm32/size
    89/<- %ecx 4/r32/esp
    #
    (_parse-array-of-decimal-ints Heap " 1 2  3  " %esi)
    (lookup *esi *(esi+4))  # => eax
    (array-equal? %ecx %eax)  # => eax
    (check-ints-equal %eax 1 "F - test-parse-array-of-decimal-ints-extra-whitespace")
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

parse-array-of-decimal-ints:  # s: (addr array byte), out: (addr handle array int)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
    (_parse-array-of-decimal-ints Heap *(ebp+8) *(ebp+0xc))
$parse-array-of-decimal-ints:end:
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return