https://github.com/akkartik/mu/blob/main/127next-word.subx
  1 # Tokenize by whitespace.
  2 
  3 == code
  4 #   instruction                     effective address                                                   register    displacement    immediate
  5 # . op          subop               mod             rm32          base        index         scale       r32
  6 # . 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
  7 
  8 # (re)compute the bounds of the next word in the line (surrounded by whitespace,
  9 # treating '#' comments as a single word)
 10 # return empty string on reaching end of file
 11 next-word:  # line: (addr stream byte), out: (addr slice)
 12     # . prologue
 13     55/push-ebp
 14     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 15     # . save registers
 16     50/push-eax
 17     51/push-ecx
 18     56/push-esi
 19     57/push-edi
 20     # esi = line
 21     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
 22     # edi = out
 23     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
 24     # skip-chars-matching-whitespace(line)
 25     # . . push args
 26     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
 27     # . . call
 28     e8/call  skip-chars-matching-whitespace/disp32
 29     # . . discard args
 30     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 31 $next-word:check0:
 32     # if (line->read >= line->write) clear out and return
 33     # . eax = line->read
 34     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy *(esi+4) to eax
 35     # . if (eax < line->write) goto next check
 36     3b/compare                      0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # compare eax with *esi
 37     7c/jump-if-<  $next-word:check-for-comment/disp8
 38     # . return out
 39     c7          0/subop/copy        0/mod/direct    7/rm32/edi    .           .             .           .           .               0/imm32           # copy to *edi
 40     c7          0/subop/copy        1/mod/*+disp8   7/rm32/edi    .           .             .           .           4/disp8         0/imm32           # copy to *(edi+4)
 41     eb/jump  $next-word:end/disp8
 42 $next-word:check-for-comment:
 43     # out->start = &line->data[line->read]
 44     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
 45     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/eax   0xc/disp8       .                 # copy esi+ecx+12 to eax
 46     89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           0/r32/eax   .               .                 # copy eax to *edi
 47     # if (line->data[line->read] == '#') out->end = &line->data[line->write]), skip rest of stream and return
 48     # . eax = line->data[line->read]
 49     31/xor                          3/mod/direct    0/rm32/eax    .           .             .           0/r32/eax   .               .                 # clear eax
 50     8a/copy-byte                    1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/AL    0xc/disp8       .                 # copy byte at *(esi+ecx+12) to AL
 51     # . compare
 52     3d/compare-eax-and  0x23/imm32/pound
 53     75/jump-if-!=  $next-word:regular-word/disp8
 54 $next-word:comment:
 55     # . out->end = &line->data[line->write]
 56     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
 57     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  0/index/eax   .           0/r32/eax   0xc/disp8       .                 # copy esi+eax+12 to eax
 58     89/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edi+4)
 59     # . line->read = line->write
 60     8b/copy                         0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # copy *esi to eax
 61     89/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(esi+4)
 62     # . return
 63     eb/jump  $next-word:end/disp8
 64 $next-word:regular-word:
 65     # otherwise skip-chars-not-matching-whitespace(line)  # including trailing newline
 66     # . . push args
 67     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
 68     # . . call
 69     e8/call  skip-chars-not-matching-whitespace/disp32
 70     # . . discard args
 71     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 72     # out->end = &line->data[line->read]
 73     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
 74     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/eax   0xc/disp8       .                 # copy esi+ecx+12 to eax
 75     89/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edi+4)
 76 $next-word:end:
 77     # . restore registers
 78     5f/pop-to-edi
 79     5e/pop-to-esi
 80     59/pop-to-ecx
 81     58/pop-to-eax
 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-next-word:
 88     # . prologue
 89     55/push-ebp
 90     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
 91     # setup
 92     # . clear-stream(_test-stream)
 93     # . . push args
 94     68/push  _test-stream/imm32
 95     # . . call
 96     e8/call  clear-stream/disp32
 97     # . . discard args
 98     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 99     # var slice/ecx: slice
100     68/push  0/imm32/end
101     68/push  0/imm32/start
102     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
103     # write(_test-stream, "  ab")
104     # . . push args
105     68/push  "  ab"/imm32
106     68/push  _test-stream/imm32
107     # . . call
108     e8/call  write/disp32
109     # . . discard args
110     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
111     # next-word(_test-stream, slice)
112     # . . push args
113     51/push-ecx
114     68/push  _test-stream/imm32
115     # . . call
116     e8/call  next-word/disp32
117     # . . discard args
118     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
119     # check-ints-equal(slice->start - _test-stream->data, 2, msg)
120     # . check-ints-equal(slice->start - _test-stream, 14, msg)
121     # . . push args
122     68/push  "F - test-next-word: start"/imm32
123     68/push  0xe/imm32
124     # . . push slice->start - _test-stream
125     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
126     81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
127     50/push-eax
128     # . . call
129     e8/call  check-ints-equal/disp32
130     # . . discard args
131     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
132     # check-ints-equal(slice->end - _test-stream->data, 4, msg)
133     # . check-ints-equal(slice->end - _test-stream, 16, msg)
134     # . . push args
135     68/push  "F - test-next-word: end"/imm32
136     68/push  0x10/imm32
137     # . . push slice->end - _test-stream
138     8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
139     81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
140     50/push-eax
141     # . . call
142     e8/call  check-ints-equal/disp32
143     # . . discard args
144     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
145     # . epilogue
146     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
147     5d/pop-to-ebp
148     c3/return
149 
150 test-next-word-returns-whole-comment:
151     # . prologue
152     55/push-ebp
153     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
154     # setup
155     # . clear-stream(_test-stream)
156     # . . push args
157     68/push  _test-stream/imm32
158     # . . call
159     e8/call  clear-stream/disp32
160     # . . discard args
161     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
162     # var slice/ecx: slice
163     68/push  0/imm32/end
164     68/push  0/imm32/start
165     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
166     # write(_test-stream, "  # a")
167     # . . push args
168     68/push  "  # a"/imm32
169     68/push  _test-stream/imm32
170     # . . call
171     e8/call  write/disp32
172     # . . discard args
173     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
174     # next-word(_test-stream, slice)
175     # . . push args
176     51/push-ecx
177     68/push  _test-stream/imm32
178     # . . call
179     e8/call  next-word/disp32
180     # . . discard args
181     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
182     # check-ints-equal(slice->start - _test-stream->data, 2, msg)
183     # . check-ints-equal(slice->start - _test-stream, 14, msg)
184     # . . push args
185     68/push  "F - test-next-word-returns-whole-comment: start"/imm32
186     68/push  0xe/imm32
187     # . . push slice->start - _test-stream
188     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy *ecx to eax
189     81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
190     50/push-eax
191     # . . call
192     e8/call  check-ints-equal/disp32
193     # . . discard args
194     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
195     # check-ints-equal(slice->end - _test-stream->data, 5, msg)
196     # . check-ints-equal(slice->end - _test-stream, 17, msg)
197     # . . push args
198     68/push  "F - test-next-word-returns-whole-comment: end"/imm32
199     68/push  0x11/imm32
200     # . . push slice->end - _test-stream
201     8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
202     81          5/subop/subtract    3/mod/direct    0/rm32/eax    .           .             .           .           .               _test-stream/imm32 # subtract from eax
203     50/push-eax
204     # . . call
205     e8/call  check-ints-equal/disp32
206     # . . discard args
207     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
208     # . epilogue
209     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
210     5d/pop-to-ebp
211     c3/return
212 
213 test-next-word-returns-empty-string-on-eof:
214     # . prologue
215     55/push-ebp
216     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
217     # setup
218     # . clear-stream(_test-stream)
219     # . . push args
220     68/push  _test-stream/imm32
221     # . . call
222     e8/call  clear-stream/disp32
223     # . . discard args
224     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
225     # var slice/ecx: slice
226     68/push  0/imm32/end
227     68/push  0/imm32/start
228     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
229     # write nothing to _test-stream
230     # next-word(_test-stream, slice)
231     # . . push args
232     51/push-ecx
233     68/push  _test-stream/imm32
234     # . . call
235     e8/call  next-word/disp32
236     # . . discard args
237     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
238     # check-ints-equal(slice->end - slice->start, 0, msg)
239     # . . push args
240     68/push  "F - test-next-word-returns-empty-string-on-eof"/imm32
241     68/push  0/imm32
242     # . . push slice->end - slice->start
243     8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
244     2b/subtract                     0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract *ecx from eax
245     50/push-eax
246     # . . call
247     e8/call  check-ints-equal/disp32
248     # . . discard args
249     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
250     # . epilogue
251     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
252     5d/pop-to-ebp
253     c3/return
254 
255 test-next-word-returns-empty-string-on-newline:
256     # . prologue
257     55/push-ebp
258     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
259     # setup
260     # . clear-stream(_test-stream)
261     # . . push args
262     68/push  _test-stream/imm32
263     # . . call
264     e8/call  clear-stream/disp32
265     # . . discard args
266     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
267     # var slice/ecx: slice
268     68/push  0/imm32/end
269     68/push  0/imm32/start
270     89/copy                         3/mod/direct    1/rm32/ecx    .           .             .           4/r32/esp   .               .                 # copy esp to ecx
271     # write some whitespace and a newline
272     # . . push args
273     68/push  "  \n"/imm32
274     68/push  _test-stream/imm32
275     # . . call
276     e8/call  write/disp32
277     # . . discard args
278     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
279     # next-word(_test-stream, slice)
280     # . . push args
281     51/push-ecx
282     68/push  _test-stream/imm32
283     # . . call
284     e8/call  next-word/disp32
285     # . . discard args
286     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
287     # check-ints-equal(slice->end - slice->start, 0, msg)
288     # . . push args
289     68/push  "F - test-next-word-returns-empty-string-on-newline"/imm32
290     68/push  0/imm32
291     # . . push slice->end - slice->start
292     8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           0/r32/eax   4/disp8         .                 # copy *(ecx+4) to eax
293     2b/subtract                     0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # subtract *ecx from eax
294     50/push-eax
295     # . . call
296     e8/call  check-ints-equal/disp32
297     # . . discard args
298     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
299     # . epilogue
300     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
301     5d/pop-to-ebp
302     c3/return
303 
304 # (re)compute the bounds of the next word in the line (separated by whitespace)
305 # return empty string on reaching end of file
306 next-raw-word:  # line: (addr stream byte), out: (addr slice)
307     # . prologue
308     55/push-ebp
309     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
310     # . save registers
311     50/push-eax
312     51/push-ecx
313     56/push-esi
314     57/push-edi
315     # esi = line
316     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
317     # edi = out
318     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
319     # skip-chars-matching-whitespace(line)
320     # . . push args
321     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
322     # . . call
323     e8/call  skip-chars-matching-whitespace/disp32
324     # . . discard args
325     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
326 $next-raw-word:check0:
327     # if (line->read >= line->write) clear out and return
328     # . eax = line->read
329     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           0/r32/eax   4/disp8         .                 # copy *(esi+4) to eax
330     # . if (eax < line->write) goto next check
331     3b/compare                      0/mod/indirect  6/rm32/esi    .           .             .           0/r32/eax   .               .                 # compare eax with *esi
332     7c/jump-if-<  $next-raw-word:word-exists/disp8
333     # . return out
334     c7          0/subop/copy        0/mod/direct    7/rm32/edi    .           .             .           .           .               0/imm32           # copy to *edi
335     c7          0/subop/copy        1/mod/*+disp8   7/rm32/edi    .           .             .           .           4/disp8         0/imm32           # copy to *(edi+4)
336     eb/jump  $next-raw-word:end/disp8
337 $next-raw-word:word-exists:
338     # out->start = &line->data[line->read]
339     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
340     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/eax   0xc/disp8       .                 # copy esi+ecx+12 to eax
341     89/copy                         0/mod/indirect  7/rm32/edi    .           .             .           0/r32/eax   .               .                 # copy eax to *edi
342     # skip-chars-not-matching-whitespace(line)  # including trailing newline
343     # . . push args
344     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
345     # . . call
346     e8/call  skip-chars-not-matching-whitespace/disp32
347     # . . discard args
348     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
349     # out->end = &line->data[line->read]
350     8b/copy                         1/mod/*+disp8   6/rm32/esi    .           .             .           1/r32/ecx   4/disp8         .                 # copy *(esi+4) to ecx
351     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    6/base/esi  1/index/ecx   .           0/r32/eax   0xc/disp8       .                 # copy esi+ecx+12 to eax
352     89/copy                         1/mod/*+disp8   7/rm32/edi    .           .             .           0/r32/eax   4/disp8         .                 # copy eax to *(edi+4)
353 $next-raw-word:end:
354     # . restore registers
355     5f/pop-to-edi
356     5e/pop-to-esi
357     59/pop-to-ecx
358     58/pop-to-eax
359     # . epilogue
360     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
361     5d/pop-to-ebp
362     c3/return