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