https://github.com/akkartik/mu/blob/main/linux/braces.subx
  1 # Structured control flow using break/loop rather than jump.
  2 #
  3 # To run (on Linux):
  4 #   $ ./translate_subx init.linux [012]*.subx subx-params.subx braces.subx
  5 #   $ mv a.elf 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 |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 |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 |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/false
 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     e8/call syscall_exit/disp32
 76 
 77 subx-braces:  # in: (addr buffered-file), out: (addr buffered-file)
 78     # pseudocode:
 79     #   var line: (stream byte 512)
 80     #   var label-stack: (stack int 32)  # at most 32 levels of nesting
 81     #   var next-label-id: int = 1
 82     #   while true
 83     #     clear-stream(line)
 84     #     read-line-buffered(in, line)
 85     #     if (line->write == 0) break                           # end of file
 86     #     skip-chars-matching-whitespace(line)
 87     #     if line->data[line->read] == '{'
 88     #       print(out, "@loop" next-label-id ":\n")
 89     #       push(label-stack, next-label-id)
 90     #       ++next-label-id
 91     #       continue
 92     #     if line->data[line->read] == '}'
 93     #       var top = pop(label-stack)
 94     #       print(out, "@break" top ":\n")
 95     #       continue
 96     #     while true
 97     #       var word-slice: (addr slice) = next-word-or-string(line)
 98     #       if slice-empty?(word-slice)                         # end of line
 99     #         break
100     #       if slice-starts-with?(word-slice, "#")              # comment
101     #         continue
102     #       if slice-starts-with?(word-slice, "break/")
103     #         var top = top(label-stack)
104     #         print(out, "@break" top)
105     #         word-slice->start += len("break")
106     #       else if slice-starts-with?(word-slice, "loop/")
107     #         var top = top(label-stack)
108     #         print(out, "@loop" top)
109     #         word-slice->start += len("loop")
110     #       print(out, word-slice " ")
111     #     print(out, "\n")
112     #   flush(out)
113     # . prologue
114     55/push-ebp
115     89/<- %ebp 4/r32/esp
116     # . save registers
117     50/push-eax
118     51/push-ecx
119     52/push-edx
120     53/push-ebx
121     56/push-esi
122     57/push-edi
123     # esi = in
124     8b/-> *(ebp+8) 6/r32/esi
125     # var line/ecx: (stream byte 512)
126     81 5/subop/subtract %esp 0x200/imm32
127     68/push 0x200/imm32/length
128     68/push 0/imm32/read
129     68/push 0/imm32/write
130     89/<- %ecx 4/r32/esp
131     # var label-stack/edx: (stack int 32)
132     81 5/subop/subtract %esp 0x80/imm32
133     68/push 0x80/imm32/length
134     68/push 0/imm32/top
135     89/<- %edx 4/r32/esp
136     # var next-label-id/ebx: int = 1
137     c7 0/subop/copy %ebx 1/imm32
138     # var word-slice/edi: slice
139     68/push 0/imm32/end
140     68/push 0/imm32/start
141     89/<- %edi 4/r32/esp
142 $subx-braces:line-loop:
143     (clear-stream %ecx)
144     (read-line-buffered %esi %ecx)
145 $subx-braces:check0:
146     # if (line->write == 0) break
147     81 7/subop/compare *ecx 0/imm32
148     0f 84/jump-if-=  $subx-braces:break/disp32
149     (skip-chars-matching-whitespace %ecx)
150 $subx-braces:check-for-curly-open:
151     # if (line->data[line->read] != '{') goto next check
152     # . eax = line->data[line->read]
153     8b/-> *(ecx+4) 0/r32/eax
154     8a/copy-byte *(ecx+eax+0xc) 0/r32/AL
155     25/and-eax-with 0xff/imm32
156     # . if (eax != '{') continue
157     3d/compare-eax-and 0x7b/imm32/open-curly
158     0f 85/jump-if-!= $subx-braces:check-for-curly-closed/disp32
159 $subx-braces:emit-curly-open:
160     # print(out, "@loop" next-label-id ":")
161     (write-buffered *(ebp+0xc) "@loop")
162     (write-int32-hex-buffered *(ebp+0xc) %ebx)
163     (write-buffered *(ebp+0xc) ":")
164     # push(label-stack, next-label-id)
165     (push %edx %ebx)
166     # ++next-label-id
167     ff 0/subop/increment %ebx
168     # continue
169     e9/jump  $subx-braces:next-line/disp32
170 $subx-braces:check-for-curly-closed:
171     # if (line->data[line->read] != '}') goto next check
172     3d/compare-eax-and 0x7d/imm32/close-curly
173     0f 85/jump-if-= $subx-braces:word-loop/disp32
174 $subx-braces:emit-curly-closed:
175     # eax = pop(label-stack)
176     (pop %edx)
177     # print(out, "@break" eax ":")
178     (write-buffered *(ebp+0xc) "@break")
179     (write-int32-hex-buffered *(ebp+0xc) %eax)
180     (write-buffered *(ebp+0xc) ":")
181     # continue
182     e9/jump  $subx-braces:next-line/disp32
183 $subx-braces:word-loop:
184     (next-word-or-string %ecx %edi)
185 $subx-braces:check1:
186     # if (slice-empty?(word-slice)) break
187     (slice-empty? %edi)
188     3d/compare-eax-and 0/imm32/false
189     0f 85/jump-if-!= $subx-braces:next-line/disp32
190 $subx-braces:check-for-comment:
191     # if (slice-starts-with?(word-slice, "#")) continue
192     # . eax = *word-slice->start
193     8b/-> *edi 0/r32/eax
194     8a/copy-byte *eax 0/r32/AL
195     25/and-eax-with 0xff/imm32
196     # . if (eax == '#') continue
197     3d/compare-eax-and 0x23/imm32/hash
198     74/jump-if-= $subx-braces:word-loop/disp8
199 $subx-braces:check-for-break:
200     # if (!slice-starts-with?(word-slice, "break/")) goto next check
201     # . eax = slice-starts-with?(word-slice, "break/")
202     (slice-starts-with? %edi "break/")
203     # . if (eax == false) goto next check
204     3d/compare-eax-and 0/imm32/false
205     74/jump-if-= $subx-braces:check-for-loop/disp8
206 $subx-braces:emit-break:
207     (top %edx)
208     # print(out, "@break" eax)
209     (write-buffered *(ebp+0xc) "@break")
210     (write-int32-hex-buffered *(ebp+0xc) %eax)
211     # word-slice->start += len("break")
212     81 0/subop/add *edi 5/imm32/strlen
213     # emit rest of word as usual
214     eb/jump $subx-braces:emit-word-slice/disp8
215 $subx-braces:check-for-loop:
216     # if (!slice-starts-with?(word-slice, "loop/")) emit word
217     # . eax = slice-starts-with?(word-slice, "loop/")
218     (slice-starts-with? %edi "loop/")
219     # . if (eax == false) goto next check
220     3d/compare-eax-and 0/imm32/false
221     74/jump-if-= $subx-braces:emit-word-slice/disp8
222 $subx-braces:emit-loop:
223     (top %edx)
224     # print(out, "@loop" eax)
225     (write-buffered *(ebp+0xc) "@loop")
226     (write-int32-hex-buffered *(ebp+0xc) %eax)
227     # word-slice->start += len("loop")
228     81 0/subop/add *edi 4/imm32/strlen
229     # fall through
230 $subx-braces:emit-word-slice:
231     # print(out, word-slice " ")
232     (write-slice-buffered *(ebp+0xc) %edi)
233     (write-buffered *(ebp+0xc) Space)
234     # loop to next word
235     e9/jump $subx-braces:word-loop/disp32
236 $subx-braces:next-line:
237     # print(out, "\n")
238     (write-buffered *(ebp+0xc) Newline)
239     # loop to next line
240     e9/jump $subx-braces:line-loop/disp32
241 $subx-braces:break:
242     (flush *(ebp+0xc))
243 $subx-braces:end:
244     # . reclaim locals
245     81 0/subop/add %esp 0x29c/imm32
246     # . restore registers
247     5f/pop-to-edi
248     5e/pop-to-esi
249     5b/pop-to-ebx
250     5a/pop-to-edx
251     59/pop-to-ecx
252     58/pop-to-eax
253     # . epilogue
254     89/<- %esp 5/r32/ebp
255     5d/pop-to-ebp
256     c3/return
257 
258 test-subx-braces-passes-most-words-through:
259     # . prologue
260     55/push-ebp
261     89/<- %ebp 4/r32/esp
262     # setup
263     (clear-stream _test-input-stream)
264     (clear-stream _test-output-stream)
265     (clear-stream $_test-input-buffered-file->buffer)
266     (clear-stream $_test-output-buffered-file->buffer)
267     # test
268     (write _test-input-stream "== abcd 0x1")
269     (subx-braces _test-input-buffered-file _test-output-buffered-file)
270     # check that the line just passed through
271     (flush _test-output-buffered-file)
272 +--  5 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------
277     (check-stream-equal _test-output-stream "== abcd 0x1 \n" "F - test-subx-braces-passes-most-words-through")
278     # . epilogue
279     89/<- %esp 5/r32/ebp
280     5d/pop-to-ebp
281     c3/return
282 
283 test-subx-braces-1:
284     # input:
285     #   {
286     #   ab break/imm32
287     #   cd loop/imm32
288     #   }
289     #
290     # output:
291     #   @loop1:
292     #   ab @break1/imm32
293     #   cd @loop1/imm32
294     #   @break1:
295     #
296     # . prologue
297     55/push-ebp
298     89/<- %ebp 4/r32/esp
299     # setup
300     (clear-stream _test-input-stream)
301     (clear-stream _test-output-stream)
302     (clear-stream $_test-input-buffered-file->buffer)
303     (clear-stream $_test-output-buffered-file->buffer)
304     # test
305     (write _test-input-stream "{\nab break/imm32\ncd loop/imm32\n}")
306     (subx-braces _test-input-buffered-file _test-output-buffered-file)
307     # check that the line just passed through
308     (flush _test-output-buffered-file)
309 +--  5 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------
314     (check-stream-equal _test-output-stream "@loop0x00000001:\nab @break0x00000001/imm32 \ncd @loop0x00000001/imm32 \n@break0x00000001:\n" "F - test-subx-braces-1")
315     # . epilogue
316     89/<- %esp 5/r32/ebp
317     5d/pop-to-ebp
318     c3/return
319 
320 test-subx-braces-2:
321     # input:
322     #   {
323     #   {
324     #   ab break/imm32
325     #   }
326     #   cd loop/imm32
327     #   }
328     #
329     # output:
330     #   @loop1:
331     #   @loop2:
332     #   ab @break2/imm32
333     #   @break2:
334     #   cd @loop1/imm32
335     #   @break1:
336     #
337     # . prologue
338     55/push-ebp
339     89/<- %ebp 4/r32/esp
340     # setup
341     (clear-stream _test-input-stream)
342     (clear-stream _test-output-stream)
343     (clear-stream $_test-input-buffered-file->buffer)
344     (clear-stream $_test-output-buffered-file->buffer)
345     # test
346     (write _test-input-stream "{\n{\nab break/imm32\n}\ncd loop/imm32\n}")
347     (subx-braces _test-input-buffered-file _test-output-buffered-file)
348     # check that the line just passed through
349     (flush _test-output-buffered-file)
350 +--  5 lines: #?     # dump _test-output-stream ----------------------------------------------------------------------------------------------------------------------------
355     (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")
356     # . epilogue
357     89/<- %esp 5/r32/ebp
358     5d/pop-to-ebp
359     c3/return