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 #   $ ./translate_subx 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/jump-if-< break/disp8
 11 #     74/jump-if-= loop/disp8
 12 #   }
 13 #   $ cat x.subx |apps/braces
 14 #   _loop1:
 15 #     7c/jump-if-< _break1/disp8
 16 #     74/jump-if-= _loop1/disp8
 17 #   _break1:
 18 #
 19 # Example 2:
 20 #   $ cat x.subx
 21 #   {
 22 #     7c/jump-if-< break/disp8
 23 #   }
 24 #   {
 25 #     74/jump-if-= loop/disp8
 26 #   }
 27 #   $ cat x.subx |apps/braces
 28 #   _loop1:
 29 #     7c/jump-if-< _break1/disp8
 30 #   _break1:
 31 #   _loop2:
 32 #     74/jump-if-= _loop2/disp8
 33 #   _break2:
 34 #
 35 # Example 3:
 36 #   $ cat x.subx
 37 #   {
 38 #     {
 39 #       74/jump-if-= loop/disp8
 40 #     }
 41 #     7c/jump-if-< loop/disp8
 42 #   }
 43 #   $ cat x.subx |apps/braces
 44 #   _loop1:
 45 #     _loop2:
 46 #       74/jump-if-= _loop2/disp8
 47 #     _break2:
 48 #     7c/jump-if-< _loop1/disp8
 49 #   _break1:
 50 
 51 == code
 52 
 53 Entry:  # run tests if necessary, a REPL if not
 54     # . prologue
 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-<= $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-= $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: (addr buffered-file), out: (addr buffered-file)
 79     # pseudocode:
 80     #   var line: (stream byte 512)
 81     #   var label-stack: (stack int 32)  # 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: (addr 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     # . prologue
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: (stream byte 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: (stack int 32)
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     # var next-label-id/ebx: int = 1
138     c7 0/subop/copy %ebx 1/imm32
139     # var word-slice/edi: slice
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-=  $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-!= $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-= $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-!= $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-= $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 == false) goto next check
205     3d/compare-eax-and 0/imm32/false
206     74/jump-if-= $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 == false) goto next check
221     3d/compare-eax-and 0/imm32/false
222     74/jump-if-= $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     # . epilogue
255     89/<- %esp 5/r32/ebp
256     5d/pop-to-ebp
257     c3/return
258 
259 test-subx-braces-passes-most-words-through:
260     # . prologue
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->buffer)
267     (clear-stream $_test-output-buffered-file->buffer)
268     # test
269     (write _test-input-stream "== abcd 0x1")
270     (subx-braces _test-input-buffered-file _test-output-buffered-file)
271     # check that the line just passed through
272     (flush _test-output-buffered-file)
273 +--  5 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
278     (check-stream-equal _test-output-stream "== abcd 0x1 \n" "F - test-subx-braces-passes-most-words-through")
279     # . epilogue
280     89/<- %esp 5/r32/ebp
281     5d/pop-to-ebp
282     c3/return
283 
284 test-subx-braces-1:
285     # input:
286     #   {
287     #   ab break/imm32
288     #   cd loop/imm32
289     #   }
290     #
291     # output:
292     #   _loop1:
293     #   ab _break1/imm32
294     #   cd _loop1/imm32
295     #   _break1:
296     #
297     # . prologue
298     55/push-ebp
299     89/<- %ebp 4/r32/esp
300     # setup
301     (clear-stream _test-input-stream)
302     (clear-stream _test-output-stream)
303     (clear-stream $_test-input-buffered-file->buffer)
304     (clear-stream $_test-output-buffered-file->buffer)
305     # test
306     (write _test-input-stream "{\nab break/imm32\ncd loop/imm32\n}")
307     (subx-braces _test-input-buffered-file _test-output-buffered-file)
308     # check that the line just passed through
309     (flush _test-output-buffered-file)
310 +--  5 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
315     (check-stream-equal _test-output-stream "_loop0x00000001:\nab _break0x00000001/imm32 \ncd _loop0x00000001/imm32 \n_break0x00000001:\n" "F - test-subx-braces-1")
316     # . epilogue
317     89/<- %esp 5/r32/ebp
318     5d/pop-to-ebp
319     c3/return
320 
321 test-subx-braces-2:
322     # input:
323     #   {
324     #   {
325     #   ab break/imm32
326     #   }
327     #   cd loop/imm32
328     #   }
329     #
330     # output:
331     #   _loop1:
332     #   _loop2:
333     #   ab _break2/imm32
334     #   _break2:
335     #   cd _loop1/imm32
336     #   _break1:
337     #
338     # . prologue
339     55/push-ebp
340     89/<- %ebp 4/r32/esp
341     # setup
342     (clear-stream _test-input-stream)
343     (clear-stream _test-output-stream)
344     (clear-stream $_test-input-buffered-file->buffer)
345     (clear-stream $_test-output-buffered-file->buffer)
346     # test
347     (write _test-input-stream "{\n{\nab break/imm32\n}\ncd loop/imm32\n}")
348     (subx-braces _test-input-buffered-file _test-output-buffered-file)
349     # check that the line just passed through
350     (flush _test-output-buffered-file)
351 +--  5 lines: #?     # dump _test-output-stream --------------------------------------------------------------------------------------------------------------
356     (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")
357     # . epilogue
358     89/<- %esp 5/r32/ebp
359     5d/pop-to-ebp
360     c3/return