5666 - start of sugar for structured control flow

This commit is contained in:
Kartik Agaram 2019-09-19 00:33:21 -07:00
parent 368212b1c3
commit 6ecddbaa92
4 changed files with 357 additions and 10 deletions

View File

@ -33,13 +33,12 @@ clear-stream: # f : (address stream) -> <void>
c7 0/subop/copy 1/mod/*+disp8 0/rm32/eax . . . . 4/disp8 0/imm32 # copy to *(eax+4)
# eax = f->data
81 0/subop/add 3/mod/direct 0/rm32/eax . . . . . 0xc/imm32 # add to eax
# while (true)
$clear-stream:loop:
# if (eax >= ecx) break
39/compare 3/mod/direct 0/rm32/eax . . . 1/r32/ecx . . # compare eax with ecx
73/jump-if-greater-or-equal-unsigned $clear-stream:end/disp8
# *eax = 0
c6 0/subop/copy 0/mod/direct 0/rm32/eax . . . . . 0/imm8 # copy byte to *eax
c6 0/subop/copy-byte 0/mod/direct 0/rm32/eax . . . . . 0/imm8 # copy byte to *eax
# ++eax
40/increment-eax
eb/jump $clear-stream:loop/disp8

BIN
apps/braces Executable file

Binary file not shown.

337
apps/braces.subx Normal file
View File

@ -0,0 +1,337 @@
# Structured control flow using break/loop rather than jump.
#
# To run (on Linux):
# $ ./ntranslate init.linux 0*.subx apps/subx-common.subx apps/calls.subx
# $ mv a.elf apps/calls
#
# Example 1:
# $ cat x.subx
# {
# 7c/if-lesser break/disp8
# 74/if-equal loop/disp8
# }
# $ cat x.subx |apps/calls
# _loop1:
# 7c/if-lesser _break1/disp8
# 74/if-equal _loop1/disp8
# _break1:
#
# Example 2:
# $ cat x.subx
# {
# 7c/if-lesser break/disp8
# }
# {
# 74/if-equal loop/disp8
# }
# $ cat x.subx |apps/calls
# _loop1:
# 7c/if-lesser _break1/disp8
# _break1:
# _loop2:
# 74/if-equal _loop2/disp8
# _break2:
#
# Example 3:
# $ cat x.subx
# {
# {
# 74/if-equal loop/disp8
# }
# 7c/if-lesser loop/disp8
# }
# $ cat x.subx |apps/calls
# _loop1:
# _loop2:
# 74/if-equal _loop2/disp8
# _break2:
# 7c/if-lesser _loop1/disp8
# _break1:
== code
Entry: # run tests if necessary, a REPL if not
# . prolog
89/<- %ebp 4/r32/esp
# initialize heap
(new-segment Heap-size Heap)
# if (argc <= 1) goto run-main
81 7/subop/compare *ebp 1/imm32
7e/jump-if-lesser-or-equal $run-main/disp8
# if (argv[1] != "test")) goto run-main
(kernel-string-equal? *(ebp+8) "test") # => eax
3d/compare-eax-and 0/imm32
74/jump-if-equal $run-main/disp8
#
(run-tests)
# syscall(exit, *Num-test-failures)
8b/-> *Num-test-failures 3/r32/ebx
eb/jump $main:end/disp8
$run-main:
(convert Stdin Stdout)
# syscall(exit, 0)
bb/copy-to-ebx 0/imm32
$main:end:
b8/copy-to-eax 1/imm32/exit
cd/syscall 0x80/imm8
convert: # in : (address buffered-file), out : (address buffered-file) -> <void>
# pseudocode:
# var line = new-stream(512, 1)
# var label-stack : (address stack) = new-stack(32*4) # at most 32 levels of nesting
# var next-label-id : int = 1
# while true
# clear-stream(line)
# read-line-buffered(in, line)
# if (line->write == 0) break # end of file
# skip-chars-matching-whitespace(line)
# if line->data[line->read] == '{'
# print(out, "_loop" next-label-id ":\n")
# push(label-stack, next-label-id)
# ++next-label-id
# if line->data[line->read] == '}'
# var top = pop(label-stack)
# print(out, "_break" top ":\n")
# while true
# var word-slice : (address slice) = next-word-or-expression(line)
# if slice-empty?(word-slice) # end of line
# break
# if slice-starts-with?(word-slice, "#") # comment
# continue
# if slice-starts-with?(word-slice, "break/")
# var top = top(label-stack)
# write(out, "_break" top)
# word-slice->start += len("break")
# else if slice-starts-with?(word-slice, "loop/")
# var top = top(label-stack)
# write(out, "_loop" top)
# word-slice->start += len("loop")
# write(out, word-slice " ")
# write(out, "\n")
# flush(out)
# . prolog
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
$convert:loop:
eb/jump $convert:loop/disp8
$convert:end:
# . restore registers
# . epilog
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
# let's just put stack primitives here for now
# we need to think about how to maintain layers of the library at different levels of syntax sugar
# A stack looks like this:
# top: int
# data: (array byte) # prefixed by length as usual
clear-stack: # s : (address stack)
# . prolog
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
50/push-eax
51/push-ecx
# eax = s
8b/-> *(ebp+8) 0/r32/eax
# ecx = s->length
8b/-> *(eax+4) 1/r32/ecx
# ecx = &s->data[s->length]
8d/copy-address *(eax+ecx+8) 1/r32/ecx
# s->top = 0
c7/copy 0/subop/copy *eax 0/imm32
# eax = s->data
81 0/subop/add %eax 8/imm32
$clear-stack:loop:
# if (eax >= ecx) break
39/compare %eax 1/r32/ecx
73/jump-if-greater-or-equal-unsigned $clear-stack:end/disp8
# *eax = 0
c6 0/subop/copy-byte *eax 0/imm8
# ++eax
40/increment-eax
eb/jump $clear-stack:loop/disp8
$clear-stack:end:
# . restore registers
59/pop-to-ecx
58/pop-to-eax
# . epilog
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
test-clear-stack:
# var ecx : (address stack) = stack of size 8 with random data in it
68/push 34/imm32
68/push 35/imm32
68/push 8/imm32/length
68/push 14/imm32/top
89/<- %ecx 4/r32/esp
# clear
(clear-stack %ecx)
# top should be 0
58/pop-to-eax
(check-ints-equal %eax 0 "F - test-clear-stack: top")
# length should remain 8
58/pop-to-eax
(check-ints-equal %eax 8 "F - test-clear-stack: length")
# first word is 0
58/pop-to-eax
(check-ints-equal %eax 0 "F - test-clear-stack: data[0..3]")
# second word is 0
58/pop-to-eax
(check-ints-equal %eax 0 "F - test-clear-stack: data[4..7]")
c3/return
push: # s : (address stack), n : int
# . prolog
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
50/push-eax
51/push-ecx
56/push-esi
# esi = s
8b/-> *(ebp+8) 6/r32/esi
# ecx = s->top
8b/-> *esi 1/r32/ecx
# if (s->top >= s->length) abort
39/compare *(esi+4) 1/r32/ecx
7e/jump-if-lesser-or-equal $push:abort/disp8
# s->data[s->top] = n
8b/-> *(ebp+0xc) 0/r32/eax
89/<- *(esi+ecx+8) 0/r32/eax
# s->top += 4
81 0/subop/add *esi 4/imm32
$push:end:
# . restore registers
5e/pop-to-esi
59/pop-to-ecx
58/pop-to-eax
# . epilog
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
$push:abort:
# print(stderr, "error: push: no space left")
# . write-buffered(Stderr, "error: push: no space left")
# . . push args
68/push "error: push: no space left"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . flush(Stderr)
# . . push args
68/push Stderr/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . syscall(exit, 1)
bb/copy-to-ebx 1/imm32
b8/copy-to-eax 1/imm32/exit
cd/syscall 0x80/imm8
# never gets here
test-push:
# var ecx : (address stack) = empty stack of size 8
68/push 0/imm32
68/push 0/imm32
68/push 8/imm32/length
68/push 0/imm32/top
89/<- %ecx 4/r32/esp
#
(push %ecx 42)
# top
58/pop-to-eax
(check-ints-equal %eax 4 "F - test-push: top")
# length
58/pop-to-eax
(check-ints-equal %eax 8 "F - test-push: length")
# first word is 42
58/pop-to-eax
(check-ints-equal %eax 42 "F - test-push: data[0..3]")
# second word is 0
58/pop-to-eax
(check-ints-equal %eax 0 "F - test-push: data[4..7]")
c3/return
pop: # s : (address stack) -> n/eax : int
# . prolog
55/push-ebp
89/<- %ebp 4/r32/esp
# . save registers
51/push-ecx
56/push-esi
# esi = s
8b/-> *(ebp+8) 6/r32/esi
# if (s->top <= 0) abort
81 7/subop/compare *esi 0/imm32
7e/jump-if-lesser-or-equal $pop:abort/disp8
# s->top -= 4
81 5/subop/subtract *esi 4/imm32
# eax = s->data[s->top]
8b/-> *esi 1/r32/ecx/top
8b/-> *(esi+ecx+8) 0/r32/eax
$pop:end:
# . restore registers
5e/pop-to-esi
59/pop-to-ecx
# . epilog
89/<- %esp 5/r32/ebp
5d/pop-to-ebp
c3/return
$pop:abort:
# print(stderr, "error: pop: nothing left in stack")
# . write-buffered(Stderr, "error: pop: nothing left in stack")
# . . push args
68/push "error: pop: nothing left in stack"/imm32
68/push Stderr/imm32
# . . call
e8/call write-buffered/disp32
# . . discard args
81 0/subop/add %esp 8/imm32
# . flush(Stderr)
# . . push args
68/push Stderr/imm32
# . . call
e8/call flush/disp32
# . . discard args
81 0/subop/add %esp 4/imm32
# . syscall(exit, 1)
bb/copy-to-ebx 1/imm32
b8/copy-to-eax 1/imm32/exit
cd/syscall 0x80/imm8
# never gets here
test-pop:
# var ecx : (address stack) = stack of size 8 containing just 42
68/push 0/imm32
68/push 42/imm32
68/push 8/imm32/length
68/push 4/imm32/top
89/<- %ecx 4/r32/esp
#
(pop %ecx) # => eax
# result
(check-ints-equal %eax 42 "F - test-pop: result")
# top
58/pop-to-eax
(check-ints-equal %eax 0 "F - test-pop: top")
# length
58/pop-to-eax
(check-ints-equal %eax 8 "F - test-pop: length")
# clean up
58/pop-to-eax
58/pop-to-eax
c3/return
== data

View File

@ -309,14 +309,9 @@ test `uname` = 'Linux' && {
echo
}
# Only native runs beyond this point. We start using syntax that the emulator
# doesn't support.
test $EMULATED && echo "skipping remaining runs in emulated mode"
test $NATIVE || exit 0
echo calls
cat 0*.subx apps/subx-common.subx apps/calls.subx | apps/sigils > a.sigils
./subx translate init.$OS a.sigils -o apps/calls
cat init.$OS 0*.subx apps/subx-common.subx apps/calls.subx | apps/sigils > a.sigils
./subx translate a.sigils -o apps/calls
[ "$1" != record ] && git diff --exit-code apps/calls
./subx run apps/calls test
echo
@ -325,6 +320,22 @@ test `uname` = 'Linux' && {
echo
}
echo braces
cat init.$OS 0*.subx apps/subx-common.subx apps/braces.subx | apps/calls | apps/sigils > a.sigils
./subx translate a.sigils -o apps/braces
[ "$1" != record ] && git diff --exit-code apps/braces
./subx run apps/braces test
echo
test `uname` = 'Linux' && {
apps/braces test
echo
}
# Only native runs beyond this point. We start using syntax that the emulator
# doesn't support.
test $EMULATED && echo "skipping remaining runs in emulated mode"
test $NATIVE || exit 0
echo "== translating using SubX"
# example programs
@ -347,7 +358,7 @@ done
# Phases of the self-hosted SubX translator.
for app in hex survey pack assort dquotes tests sigils calls
for app in hex survey pack assort dquotes tests sigils calls braces
do
echo $app
./ntranslate init.$OS 0*.subx apps/subx-common.subx apps/$app.subx