https://github.com/akkartik/mu/blob/master/apps/braces.subx
  1 # Structured control flow using break/loop rather than jump.
  2 #
  3 # To run (on Linux):
  4 #   $ ./ntranslate init.linux 0*.subx apps/subx-params.subx apps/braces.subx
  5 #   $ mv a.elf apps/braces
  6 #
  7 # Example 1:
  8 #   $ cat x.subx
  9 #   {
 10 #     7c/if-lesser break/disp8
 11 #     74/if-equal loop/disp8
 12 #   }
 13 #   $ cat x.subx |apps/braces
 14 #   _loop1:
 15 #     7c/if-lesser _break1/disp8
 16 #     74/if-equal _loop1/disp8
 17 #   _break1:
 18 #
 19 # Example 2:
 20 #   $ cat x.subx
 21 #   {
 22 #     7c/if-lesser break/disp8
 23 #   }
 24 #   {
 25 #     74/if-equal loop/disp8
 26 #   }
 27 #   $ cat x.subx |apps/braces
 28 #   _loop1:
 29 #     7c/if-lesser _break1/disp8
 30 #   _break1:
 31 #   _loop2:
 32 #     74/if-equal _loop2/disp8
 33 #   _break2:
 34 #
 35 # Example 3:
 36 #   $ cat x.subx
 37 #   {
 38 #     {
 39 #       74/if-equal loop/disp8
 40 #     }
 41 #     7c/if-lesser loop/disp8
 42 #   }
 43 #   $ cat x.subx |apps/braces
 44 #   _loop1:
 45 #     _loop2:
 46 #       74/if-equal _loop2/disp8
 47 #     _break2:
 48 #     7c/if-lesser _loop1/disp8
 49 #   _break1:
 50 
 51 == code
 52 
 53 Entry:  # run tests if necessary, a REPL if not
 54     # . prolog
 55     89/<- %ebp 4/r32/esp
 56     # initialize heap
 57     (new-segment Heap-size Heap)
 58     # if (argc <= 1) goto interactive
 59     81 7/subop/compare *ebp 1/imm32
 60     7e/jump-if-lesser-or-equal $subx-braces-main:interactive/disp8
 61     # if (argv[1] != "test")) goto interactive
 62     (kernel-string-equal? *(ebp+8) "test")  # => eax
 63     3d/compare-eax-and 0/imm32
 64     74/jump-if-equal $subx-braces-main:interactive/disp8
 65     #
 66     (run-tests)
 67     # syscall(exit, *Num-test-failures)
 68     8b/-> *Num-test-failures 3/r32/ebx
 69     eb/jump $subx-braces-main:end/disp8
 70 $subx-braces-main:interactive:
 71     (subx-braces Stdin Stdout)
 72     # syscall(exit, 0)
 73     bb/copy-to-ebx 0/imm32
 74 $subx-braces-main:end:
 75     b8/copy-to-eax 1/imm32/exit
 76     cd/syscall 0x80/imm8
 77 
 78 subx-braces:  # in : (address buffered-file), out : (address buffered-file) -> <void>
 79     # pseudocode:
 80     #   var line = new-stream(512, 1)
 81     #   var label-stack : (address stack) = new-stack(32*4)  # at most 32 levels of nesting
 82     #   var next-label-id : int = 1
 83     #   while true
 84     #     clear-stream(line)
 85     #     read-line-buffered(in, line)
 86     #     if (line->write == 0) break                           # end of file
 87     #     skip-chars-matching-whitespace(line)
 88     #     if line->data[line->read] == '{'
 89     #       print(out, "_loop" next-label-id ":\n")
 90     #       push(label-stack, next-label-id)
 91     #       ++next-label-id
 92     #       continue
 93     #     if line->data[line->read] == '}'
 94     #       var top = pop(label-stack)
 95     #       print(out, "_break" top ":\n")
 96     #       continue
 97     #     while true
 98     #       var word-slice : (address slice) = next-word-or-string(line)
 99     #       if slice-empty?(word-slice)                         # end of line
100     #         break
101     #       if slice-starts-with?(word-slice, "#")              # comment
102     #         continue
103     #       if slice-starts-with?(word-slice, "break/")
104     #         var top = top(label-stack)
105     #         print(out, "_break" top)
106     #         word-slice->start += len("break")
107     #       else if slice-starts-with?(word-slice, "loop/")
108     #         var top = top(label-stack)
109     #         print(out, "_loop" top)
110     #         word-slice->start += len("loop")
111     #       print(out, word-slice " ")
112     #     print(out, "\n")
113     #   flush(out)
114     # . prolog
115     55/push-ebp
116     89/<- %ebp 4/r32/esp
117     # . save registers
118     50/push-eax
119     51/push-ecx
120     52/push-edx
121     53/push-ebx
122     56/push-esi
123     57/push-edi
124     # esi = in
125     8b/-> *(ebp+8) 6/r32/esi
126     # var line/ecx : (address stream byte) = stream(512)
127     81 5/subop/subtract %esp 0x200/imm32
128     68/push 0x200/imm32/length
129     68/push 0/imm32/read
130     68/push 0/imm32/write
131     89/<- %ecx 4/r32/esp
132     # var label-stack/edx : (address stack)
133     81 5/subop/subtract %esp 0x80/imm32
134     68/push 0x80/imm32/length
135     68/push 0/imm32/top
136     89/<- %edx 4/r32/esp
137     # next-label-id/ebx = 1
138     c7 0/subop/copy %ebx 1/imm32
139     # var word-slice/edi = {0, 0}
140     68/push 0/imm32/end
141     68/push 0/imm32/start
142     89/<- %edi 4/r32/esp
143 $subx-braces:line-loop:
144     (clear-stream %ecx)
145     (read-line-buffered %esi %ecx)
146 $subx-braces:check0:
147     # if (line->write == 0) break
148     81 7/subop/compare *ecx 0/imm32
149     0f 84/jump-if-equal  $subx-braces:break/disp32
150     (skip-chars-matching-whitespace %ecx)
151 $subx-braces:check-for-curly-open:
152     # if (line->data[line->read] != '{') goto next check
153     # . eax = line->data[line->read]
154     8b/-> *(ecx+4) 0/r32/eax
155     8a/copy-byte *(ecx+eax+0xc) 0/r32/AL
156     81 4/subop/and %eax 0xff/imm32
157     # . if (eax != '{') continue
158     3d/compare-eax-and 0x7b/imm32/open-curly
159     0f 85/jump-if-not-equal $subx-braces:check-for-curly-closed/disp32
160 $subx-braces:emit-curly-open:
161     # print(out, "_loop" next-label-id ":")
162     (write-buffered *(ebp+0xc) "_loop")
163     (print-int32-buffered *(ebp+0xc) %ebx)
164     (write-buffered *(ebp+0xc) ":")
165     # push(label-stack, next-label-id)
166     (push %edx %ebx)
167     # ++next-label-id
168     ff 0/subop/increment %ebx
169     # continue
170     e9/jump  $subx-braces:next-line/disp32
171 $subx-braces:check-for-curly-closed:
172     # if (line->data[line->read] != '}') goto next check
173     3d/compare-eax-and 0x7d/imm32/close-curly
174     0f 85/jump-if-equal $subx-braces:word-loop/disp32
175 $subx-braces:emit-curly-closed:
176     # eax = pop(label-stack)
177     (pop %edx)
178     # print(out, "_break" eax ":")
179     (write-buffered *(ebp+0xc) "_break")
180     (print-int32-buffered *(ebp+0xc) %eax)
181     (write-buffered *(ebp+0xc) ":")
182     # continue
183     e9/jump  $subx-braces:next-line/disp32
184 $subx-braces:word-loop:
185     (next-word-or-string %ecx %edi)
186 $subx-braces:check1:
187     # if (slice-empty?(word-slice)) break
188     (slice-empty? %edi)
189     3d/compare-eax-and 0/imm32
190     0f 85/jump-if-not-equal $subx-braces:next-line/disp32
191 $subx-braces:check-for-comment:
192     # if (slice-starts-with?(word-slice, "#")) continue
193     # . eax = *word-slice->start
194     8b/-> *edi 0/r32/eax
195     8a/copy-byte *eax 0/r32/AL
196     81 4/subop/and %eax 0xff/imm32
197     # . if (eax == '#') continue
198     3d/compare-eax-and 0x23/imm32/hash
199     74/jump-if-equal $subx-braces:word-loop/disp8
200 $subx-braces:check-for-break:
201     # if (!slice-starts-with?(word-slice, "break/")) goto next check
202     # . eax = slice-starts-with?(word-slice, "break/")
203     (slice-starts-with? %edi "break/")
204     # . if (eax == 0) goto next check
205     3d/compare-eax-and 0/imm32
206     74/jump-if-equal $subx-braces:check-for-loop/disp8
207 $subx-braces:emit-break:
208     (top %edx)
209     # print(out, "_break" eax)
210     (write-buffered *(ebp+0xc) "_break")
211     (print-int32-buffered *(ebp+0xc) %eax)
212     # word-slice->start += len("break")
213     81 0/subop/add *edi 5/imm32/strlen
214     # emit rest of word as usual
215     eb/jump $subx-braces:emit-word-slice/disp8
216 $subx-braces:check-for-loop:
217     # if (!slice-starts-with?(word-slice, "loop/")) emit word
218     # . eax = slice-starts-with?(word-slice, "loop/")
219     (slice-starts-with? %edi "loop/")
220     # . if (eax == 0) goto next check
221     3d/compare-eax-and 0/imm32
222     74/jump-if-equal $subx-braces:emit-word-slice/disp8
223 $subx-braces:emit-loop:
224     (top %edx)
225     # print(out, "_loop" eax)
226     (write-buffered *(ebp+0xc) "_loop")
227     (print-int32-buffered *(ebp+0xc) %eax)
228     # word-slice->start += len("loop")
229     81 0/subop/add *edi 4/imm32/strlen
230     # fall through
231 $subx-braces:emit-word-slice:
232     # print(out, word-slice " ")
233     (write-slice-buffered *(ebp+0xc) %edi)
234     (write-buffered *(ebp+0xc) Space)
235     # loop to next word
236     e9/jump $subx-braces:word-loop/disp32
237 $subx-braces:next-line:
238     # print(out, "\n")
239     (write-buffered *(ebp+0xc) Newline)
240     # loop to next line
241     e9/jump $subx-braces:line-loop/disp32
242 $subx-braces:break:
243     (flush *(ebp+0xc))
244 $subx-braces:end:
245     # . reclaim locals
246     81 0/subop/add %esp 0x29c/imm32
247     # . restore registers
248     5f/pop-to-edi
249     5e/pop-to-esi
250     5b/pop-to-ebx
251     5a/pop-to-edx
252     59/pop-to-ecx
253     58/pop-to-eax
254     # . epilog
255     89/<- %esp 5/r32/ebp
256     5d/pop-to-ebp
257     c3/return
258 
259 test-subx-braces-passes-most-words-through:
260     # . prolog
261     55/push-ebp
262     89/<- %ebp 4/r32/esp
263     # setup
264     (clear-stream _test-input-stream)
265     (clear-stream _test-output-stream)
266     # . clear-stream(_test-input-buffered-file+4)
267     b8/copy-to-eax  _test-input-buffered-file/imm32
268     05/add-to-eax  4/imm32
269     (clear-stream %eax)
270     # . clear-stream(_test-output-buffered-file+4)
271     b8/copy-to-eax  _test-output-buffered-file/imm32
272     05/add-to-eax  4/imm32
273     (clear-stream %eax)
274     # test
275     (write _test-input-stream "== abcd 0x1")
276     (subx-braces _test-input-buffered-file _test-output-buffered-file)
277     # check that the line just passed through
278     (flush _test-output-buffered-file)
279 +--  5 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
284     (check-stream-equal _test-output-stream "== abcd 0x1 \n" "F - test-subx-braces-passes-most-words-through")
285     # . epilog
286     89/<- %esp 5/r32/ebp
287     5d/pop-to-ebp
288     c3/return
289 
290 test-subx-braces-1:
291     # input:
292     #   {
293     #   ab break/imm32
294     #   cd loop/imm32
295     #   }
296     #
297     # output:
298     #   _loop1:
299     #   ab _break1/imm32
300     #   cd _loop1/imm32
301     #   _break1:
302     #
303     # . prolog
304     55/push-ebp
305     89/<- %ebp 4/r32/esp
306     # setup
307     (clear-stream _test-input-stream)
308     (clear-stream _test-output-stream)
309     # . clear-stream(_test-input-buffered-file+4)
310     b8/copy-to-eax  _test-input-buffered-file/imm32
311     05/add-to-eax  4/imm32
312     (clear-stream %eax)
313     # . clear-stream(_test-output-buffered-file+4)
314     b8/copy-to-eax  _test-output-buffered-file/imm32
315     05/add-to-eax  4/imm32
316     (clear-stream %eax)
317     # test
318     (write _test-input-stream "{\nab break/imm32\ncd loop/imm32\n}")
319     (subx-braces _test-input-buffered-file _test-output-buffered-file)
320     # check that the line just passed through
321     (flush _test-output-buffered-file)
322 +--  5 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
327     (check-stream-equal _test-output-stream "_loop0x00000001:\nab _break0x00000001/imm32 \ncd _loop0x00000001/imm32 \n_break0x00000001:\n" "F - test-subx-braces-1")
328     # . epilog
329     89/<- %esp 5/r32/ebp
330     5d/pop-to-ebp
331     c3/return
332 
333 test-subx-braces-2:
334     # input:
335     #   {
336     #   {
337     #   ab break/imm32
338     #   }
339     #   cd loop/imm32
340     #   }
341     #
342     # output:
343     #   _loop1:
344     #   _loop2:
345     #   ab _break2/imm32
346     #   _break2:
347     #   cd _loop1/imm32
348     #   _break1:
349     #
350     # . prolog
351     55/push-ebp
352     89/<- %ebp 4/r32/esp
353     # setup
354     (clear-stream _test-input-stream)
355     (clear-stream _test-output-stream)
356     # . clear-stream(_test-input-buffered-file+4)
357     b8/copy-to-eax  _test-input-buffered-file/imm32
358     05/add-to-eax  4/imm32
359     (clear-stream %eax)
360     # . clear-stream(_test-output-buffered-file+4)
361     b8/copy-to-eax  _test-output-buffered-file/imm32
362     05/add-to-eax  4/imm32
363     (clear-stream %eax)
364     # test
365     (write _test-input-stream "{\n{\nab break/imm32\n}\ncd loop/imm32\n}")
366     (subx-braces _test-input-buffered-file _test-output-buffered-file)
367     # check that the line just passed through
368     (flush _test-output-buffered-file)
369 +--  5 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
374     (check-stream-equal _test-output-stream "_loop0x00000001:\n_loop0x00000002:\nab _break0x00000002/imm32 \n_break0x00000002:\ncd _loop0x00000001/imm32 \n_break0x00000001:\n" "F - test-subx-braces-2")
375     # . epilog
376     89/<- %esp 5/r32/ebp
377     5d/pop-to-ebp
378     c3/return
379 
380 # let's just put stack primitives here for now
381 # we need to think about how to maintain layers of the library at different levels of syntax sugar
382 
383 # A stack looks like this:
384 #   top: int
385 #   data: (array byte)  # prefixed by length as usual
386 
387 clear-stack:  # s : (address stack)
388     # . prolog
389     55/push-ebp
390     89/<- %ebp 4/r32/esp
391     # . save registers
392     50/push-eax
393     51/push-ecx
394     # eax = s
395     8b/-> *(ebp+8) 0/r32/eax
396     # ecx = s->length
397     8b/-> *(eax+4) 1/r32/ecx
398     # ecx = &s->data[s->length]
399     8d/copy-address *(eax+ecx+8) 1/r32/ecx
400     # s->top = 0
401     c7/copy 0/subop/copy *eax 0/imm32
402     # eax = s->data
403     81 0/subop/add %eax 8/imm32
404 $clear-stack:loop:
405     # if (eax >= ecx) break
406     39/compare %eax 1/r32/ecx
407     73/jump-if-greater-or-equal-unsigned $clear-stack:end/disp8
408     # *eax = 0
409     c6 0/subop/copy-byte *eax 0/imm8
410     # ++eax
411     40/increment-eax
412     eb/jump $clear-stack:loop/disp8
413 $clear-stack:end:
414     # . restore registers
415     59/pop-to-ecx
416     58/pop-to-eax
417     # . epilog
418     89/<- %esp 5/r32/ebp
419     5d/pop-to-ebp
420     c3/return
421 
422 test-clear-stack:
423     # var ecx : (address stack) = stack of size 8 with random data in it
424     68/push 34/imm32
425     68/push 35/imm32
426     68/push 8/imm32/length
427     68/push 14/imm32/top
428     89/<- %ecx 4/r32/esp
429     # clear
430     (clear-stack %ecx)
431     # top should be 0
432     58/pop-to-eax
433     (check-ints-equal %eax 0 "F - test-clear-stack: top")
434     # length should remain 8
435     58/pop-to-eax
436     (check-ints-equal %eax 8 "F - test-clear-stack: length")
437     # first word is 0
438     58/pop-to-eax
439     (check-ints-equal %eax 0 "F - test-clear-stack: data[0..3]")
440     # second word is 0
441     58/pop-to-eax
442     (check-ints-equal %eax 0 "F - test-clear-stack: data[4..7]")
443     c3/return
444 
445 push:  # s : (address stack), n : int
446     # . prolog
447     55/push-ebp
448     89/<- %ebp 4/r32/esp
449     # . save registers
450     50/push-eax
451     51/push-ecx
452     56/push-esi
453     # esi = s
454     8b/-> *(ebp+8) 6/r32/esi
455     # ecx = s->top
456     8b/-> *esi 1/r32/ecx
457     # if (s->top >= s->length) abort
458     39/compare *(esi+4) 1/r32/ecx
459     7e/jump-if-lesser-or-equal $push:abort/disp8
460     # s->data[s->top] = n
461     8b/-> *(ebp+0xc) 0/r32/eax
462     89/<- *(esi+ecx+8) 0/r32/eax
463     # s->top += 4
464     81 0/subop/add *esi 4/imm32
465 $push:end:
466     # . restore registers
467     5e/pop-to-esi
468     59/pop-to-ecx
469     58/pop-to-eax
470     # . epilog
471     89/<- %esp 5/r32/ebp
472     5d/pop-to-ebp
473     c3/return
474 
475 $push:abort:
476     # print(stderr, "error: push: no space left")
477     # . write-buffered(Stderr, "error: push: no space left")
478     # . . push args
479     68/push "error: push: no space left"/imm32
480     68/push Stderr/imm32
481     # . . call
482     e8/call write-buffered/disp32
483     # . . discard args
484     81 0/subop/add %esp 8/imm32
485     # . flush(Stderr)
486     # . . push args
487     68/push Stderr/imm32
488     # . . call
489     e8/call flush/disp32
490     # . . discard args
491     81 0/subop/add %esp 4/imm32
492     # . syscall(exit, 1)
493     bb/copy-to-ebx 1/imm32
494     b8/copy-to-eax 1/imm32/exit
495     cd/syscall 0x80/imm8
496     # never gets here
497 
498 test-push:
499     # var ecx : (address stack) = empty stack of size 8
500     68/push 0/imm32
501     68/push 0/imm32
502     68/push 8/imm32/length
503     68/push 0/imm32/top
504     89/<- %ecx 4/r32/esp
505     #
506     (push %ecx 42)
507     # top
508     58/pop-to-eax
509     (check-ints-equal %eax 4 "F - test-push: top")
510     # length
511     58/pop-to-eax
512     (check-ints-equal %eax 8 "F - test-push: length")
513     # first word is 42
514     58/pop-to-eax
515     (check-ints-equal %eax 42 "F - test-push: data[0..3]")
516     # second word is 0
517     58/pop-to-eax
518     (check-ints-equal %eax 0 "F - test-push: data[4..7]")
519     c3/return
520 
521 pop:  # s : (address stack) -> n/eax : int
522     # . prolog
523     55/push-ebp
524     89/<- %ebp 4/r32/esp
525     # . save registers
526     51/push-ecx
527     56/push-esi
528     # esi = s
529     8b/-> *(ebp+8) 6/r32/esi
530     # if (s->top <= 0) abort
531     81 7/subop/compare *esi 0/imm32
532     7e/jump-if-lesser-or-equal $pop:abort/disp8
533     # s->top -= 4
534     81 5/subop/subtract *esi 4/imm32
535     # eax = s->data[s->top]
536     8b/-> *esi 1/r32/ecx/top
537     8b/-> *(esi+ecx+8) 0/r32/eax
538 $pop:end:
539     # . restore registers
540     5e/pop-to-esi
541     59/pop-to-ecx
542     # . epilog
543     89/<- %esp 5/r32/ebp
544     5d/pop-to-ebp
545     c3/return
546 
547 $pop:abort:
548     # print(stderr, "error: pop: nothing left in stack")
549     # . write-buffered(Stderr, "error: pop: nothing left in stack")
550     # . . push args
551     68/push "error: pop: nothing left in stack"/imm32
552     68/push Stderr/imm32
553     # . . call
554     e8/call write-buffered/disp32
555     # . . discard args
556     81 0/subop/add %esp 8/imm32
557     # . flush(Stderr)
558     # . . push args
559     68/push Stderr/imm32
560     # . . call
561     e8/call flush/disp32
562     # . . discard args
563     81 0/subop/add %esp 4/imm32
564     # . syscall(exit, 1)
565     bb/copy-to-ebx 1/imm32
566     b8/copy-to-eax 1/imm32/exit
567     cd/syscall 0x80/imm8
568     # never gets here
569 
570 test-pop:
571     # var ecx : (address stack) = stack of size 8 containing just 42
572     68/push 0/imm32
573     68/push 42/imm32
574     68/push 8/imm32/length
575     68/push 4/imm32/top
576     89/<- %ecx 4/r32/esp
577     #
578     (pop %ecx)  # => eax
579     # result
580     (check-ints-equal %eax 42 "F - test-pop: result")
581     # top
582     58/pop-to-eax
583     (check-ints-equal %eax 0 "F - test-pop: top")
584     # length
585     58/pop-to-eax
586     (check-ints-equal %eax 8 "F - test-pop: length")
587     # clean up
588     81 0/subop/add %esp 8/imm32
589     c3/return
590 
591 top:  # s : (address stack) -> n/eax : int
592     # . prolog
593     55/push-ebp
594     89/<- %ebp 4/r32/esp
595     # . save registers
596     51/push-ecx
597     56/push-esi
598     # esi = s
599     8b/-> *(ebp+8) 6/r32/esi
600     # if (s->top <= 0) abort
601     81 7/subop/compare *esi 0/imm32
602     7e/jump-if-lesser-or-equal $top:abort/disp8
603     # eax = s->data[s->top - 4]
604     8b/-> *esi 1/r32/ecx/top
605     81 5/subop/subtract %ecx 4/imm32
606     8b/-> *(esi+ecx+8) 0/r32/eax
607 $top:end:
608     # . restore registers
609     5e/pop-to-esi
610     59/pop-to-ecx
611     # . epilog
612     89/<- %esp 5/r32/ebp
613     5d/pop-to-ebp
614     c3/return
615 
616 $top:abort:
617     # print(stderr, "error: top: nothing left in stack")
618     # . write-buffered(Stderr, "error: top: nothing left in stack")
619     # . . push args
620     68/push "error: top: nothing left in stack"/imm32
621     68/push Stderr/imm32
622     # . . call
623     e8/call write-buffered/disp32
624     # . . discard args
625     81 0/subop/add %esp 8/imm32
626     # . flush(Stderr)
627     # . . push args
628     68/push Stderr/imm32
629     # . . call
630     e8/call flush/disp32
631     # . . discard args
632     81 0/subop/add %esp 4/imm32
633     # . syscall(exit, 1)
634     bb/copy-to-ebx 1/imm32
635     b8/copy-to-eax 1/imm32/exit
636     cd/syscall 0x80/imm8
637     # never gets here
638 
639 test-top:
640     # var ecx : (address stack) = stack of size 8 containing just 42
641     68/push 0/imm32
642     68/push 42/imm32
643     68/push 8/imm32/length
644     68/push 4/imm32/top
645     89/<- %ecx 4/r32/esp
646     #
647     (top %ecx)  # => eax
648     # result
649     (check-ints-equal %eax 42 "F - test-top: result")
650     # clean up
651     81 0/subop/add %esp 0x10/imm32
652     c3/return