about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik Agaram <vc@akkartik.com>2020-09-15 22:21:15 -0700
committerKartik Agaram <vc@akkartik.com>2020-09-15 22:52:41 -0700
commitae470b42f102d5da4f7d4255a47e3cf582079f33 (patch)
treeaa251ea7734370b9c152a5cf08a7b97c9c1de0c1
parent8815cf7d57e738731dfc43680b4eccbaef9d822c (diff)
downloadmu-ae470b42f102d5da4f7d4255a47e3cf582079f33.tar.gz
6781 - new app: RPN (postfix) calculator
This was surprisingly hard; bugs discovered all over the place.
-rw-r--r--118parse-hex-int.subx6
-rw-r--r--127next-word.subx7
-rw-r--r--304screen.subx19
-rw-r--r--305keyboard.subx16
-rw-r--r--308allocate-array.subx2
-rw-r--r--311parse-decimal-int.subx233
-rw-r--r--400.mu12
-rwxr-xr-xapps/assortbin44513 -> 44508 bytes
-rwxr-xr-xapps/bracesbin46376 -> 46371 bytes
-rwxr-xr-xapps/callsbin51023 -> 51018 bytes
-rwxr-xr-xapps/crenshaw2-1bin43854 -> 43849 bytes
-rwxr-xr-xapps/crenshaw2-1bbin44401 -> 44396 bytes
-rwxr-xr-xapps/dquotesbin48135 -> 48130 bytes
-rwxr-xr-xapps/factorialbin42957 -> 42952 bytes
-rwxr-xr-xapps/hexbin46693 -> 46688 bytes
-rwxr-xr-xapps/mubin389400 -> 390307 bytes
-rw-r--r--apps/mu.subx7
-rwxr-xr-xapps/packbin57092 -> 57087 bytes
-rw-r--r--apps/rpn.mu149
-rwxr-xr-xapps/sigilsbin58745 -> 58740 bytes
-rwxr-xr-xapps/surveybin54445 -> 54440 bytes
-rwxr-xr-xapps/testsbin43285 -> 43299 bytes
-rw-r--r--apps/tests.subx12
23 files changed, 451 insertions, 12 deletions
diff --git a/118parse-hex-int.subx b/118parse-hex-int.subx
index ebbd4e4e..5d92b13a 100644
--- a/118parse-hex-int.subx
+++ b/118parse-hex-int.subx
@@ -393,7 +393,7 @@ parse-hex-int-from-slice:  # in: (addr slice) -> result/eax: int
     52/push-edx
     # ecx = in
     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
-    # edx = in->end
+    # var max/edx: (addr byte) = in->end
     8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           2/r32/edx   4/disp8         .                 # copy *(ecx+4) to edx
     # var curr/ecx: (addr byte) = in->start
     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           1/r32/ecx   .               .                 # copy *ecx to ecx
@@ -582,7 +582,7 @@ test-parse-hex-int-from-slice-0x-prefix:
     e8/call  parse-hex-int-from-slice/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 0x34a, msg)
+    # check-ints-equal(eax, 0x34, msg)
     # . . push args
     68/push  "F - test-parse-hex-int-from-slice-0x-prefix"/imm32
     68/push  0x34/imm32
@@ -616,7 +616,7 @@ test-parse-hex-int-from-slice-zero:
     e8/call  parse-hex-int-from-slice/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
-    # check-ints-equal(eax, 0x34a, msg)
+    # check-ints-equal(eax, 0, msg)
     # . . push args
     68/push  "F - test-parse-hex-int-from-slice-zero"/imm32
     68/push  0/imm32
diff --git a/127next-word.subx b/127next-word.subx
index c3e9762d..6a186656 100644
--- a/127next-word.subx
+++ b/127next-word.subx
@@ -20,14 +20,13 @@ next-word:  # line: (addr stream byte), out: (addr slice)
     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           6/r32/esi   8/disp8         .                 # copy *(ebp+8) to esi
     # edi = out
     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           7/r32/edi   0xc/disp8       .                 # copy *(ebp+12) to edi
-    # skip-chars-matching(line, ' ')
+    # skip-chars-matching-whitespace(line)
     # . . push args
-    68/push  0x20/imm32/space
     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
     # . . call
-    e8/call  skip-chars-matching/disp32
+    e8/call  skip-chars-matching-whitespace/disp32
     # . . discard args
-    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
 $next-word:check0:
     # if (line->read >= line->write) clear out and return
     # . eax = line->read
diff --git a/304screen.subx b/304screen.subx
index 1537a9b1..8ec33fd5 100644
--- a/304screen.subx
+++ b/304screen.subx
@@ -131,6 +131,19 @@ $print-string-to-real-screen:end:
     5d/pop-to-ebp
     c3/return
 
+print-slice-to-real-screen:  # s: (addr slice)
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    #
+    (write-slice-buffered Stdout *(ebp+8))
+    (flush Stdout)
+$print-slice-to-real-screen:end:
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 print-stream-to-real-screen:  # s: (addr stream byte)
     # . prologue
     55/push-ebp
@@ -240,6 +253,8 @@ write-int32-decimal-buffered:  # f: (addr buffered-file), n: int
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
+    # . save registers
+    51/push-ecx
     # var ecx: (stream byte 16)
     81 5/subop/subtract %esp 0x10/imm32
     68/push 0x10/imm32/size
@@ -249,6 +264,10 @@ write-int32-decimal-buffered:  # f: (addr buffered-file), n: int
     (write-int32-decimal %ecx *(ebp+0xc))
     (write-stream-data *(ebp+8) %ecx)
 $write-int32-decimal-buffered:end:
+    # . reclaim locals
+    81 0/subop/add %esp 0x1c/imm32
+    # . restore registers
+    59/pop-to-ecx
     # . epilogue
     89/<- %esp 5/r32/ebp
     5d/pop-to-ebp
diff --git a/305keyboard.subx b/305keyboard.subx
index e1a9cc0e..7c9ce065 100644
--- a/305keyboard.subx
+++ b/305keyboard.subx
@@ -144,6 +144,22 @@ $read-key-from-real-keyboard:end:
     5d/pop-to-ebp
     c3/return
 
+read-line-from-real-keyboard:  # in: (addr stream byte)
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    50/push-eax
+    #
+    (read 0 *(ebp+8))  # => eax
+$read-line-from-real-keyboard:end:
+    # . restore registers
+    58/pop-to-eax
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
 == data
 
 # iflags:   octal     hex
diff --git a/308allocate-array.subx b/308allocate-array.subx
index 760b8090..c243ee33 100644
--- a/308allocate-array.subx
+++ b/308allocate-array.subx
@@ -11,7 +11,7 @@ allocate-array2:  # ad: (addr allocation-descriptor), array-len: int, elem-size:
     52/push-edx
     #
     8b/-> *(ebp+0xc) 0/r32/eax
-    f7 4/subop/multiply-into-eax-edx *(ebp+0x10)
+    f7 4/subop/multiply-into-edx-eax *(ebp+0x10)
     # TODO: check edx for overflow
     (allocate-array *(ebp+8) %eax *(ebp+0x14))
 $allocate-array2:end:
diff --git a/311parse-decimal-int.subx b/311parse-decimal-int.subx
new file mode 100644
index 00000000..538f3e87
--- /dev/null
+++ b/311parse-decimal-int.subx
@@ -0,0 +1,233 @@
+# Helpers for parsing decimal ints.
+
+parse-decimal-int-from-slice:  # in: (addr slice) -> out/eax: int    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    51/push-ecx
+    # ecx = in
+    8b/-> *(ebp+8) 1/r32/ecx
+    #
+    (parse-decimal-int-helper *ecx *(ecx+4))  # => eax
+$parse-decimal-int-from-slice:end:
+    # . restore registers
+    59/pop-to-ecx
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+parse-decimal-int-helper:  # start: (addr byte), end: (addr byte) -> result/eax: int
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    51/push-ecx
+    52/push-edx
+    53/push-ebx
+    56/push-esi
+    57/push-edi
+    # var curr/esi: (addr byte) = start
+    8b/-> *(ebp+8) 6/r32/esi
+    # edi = end
+    8b/-> *(ebp+0xc) 7/r32/edi
+    # var negate?/edx: boolean = false
+    ba/copy-to-edx 0/imm32/false
+    # if (*curr == '-') ++curr, negate = true
+    {
+$parse-decimal-int-helper:negative:
+      b8/copy-to-eax 0/imm32
+      8a/copy-byte *esi 0/r32/AL
+      3d/compare-eax-and 0x2d/imm32/-
+      75/jump-if-!= break/disp8
+      # . ++curr
+      46/increment-esi
+      # . negate = true
+      ba/copy-to-edx  1/imm32/true
+    }
+    # spill negate?
+    52/push-edx
+    # var result/eax: int = 0
+    b8/copy-to-eax 0/imm32
+    # var digit/ecx: int = 0
+    b9/copy-to-ecx 0/imm32
+    # const TEN/ebx: int = 10
+    bb/copy-to-ebx 0xa/imm32
+    {
+$parse-decimal-int-helper:loop:
+      # if (curr >= in->end) break
+      39/compare %esi 7/r32/edi
+      73/jump-if-addr>= break/disp8
+      # digit = from-decimal-char(*curr)
+      8a/copy-byte *esi 1/r32/CL
+      81 5/subop/subtract %ecx 0x30/imm32/zero
+      # TODO: error checking
+      # result = result * 10 + digit
+      ba/copy-to-edx 0/imm32
+      f7 4/subop/multiply-into-edx-eax %ebx
+      # TODO: check edx for overflow
+      01/add %eax 1/r32/ecx
+      # ++curr
+      46/increment-esi
+      #
+      eb/jump loop/disp8
+    }
+$parse-decimal-int-helper:negate:
+    # if (negate?) result = -result
+    5a/pop-to-edx
+    {
+      81 7/subop/compare %edx 0/imm32/false
+      74/jump-if-= break/disp8
+      f7 3/subop/negate %eax
+    }
+$parse-decimal-int-helper:end:
+    # . restore registers
+    5f/pop-to-edi
+    5e/pop-to-esi
+    5b/pop-to-ebx
+    5a/pop-to-edx
+    59/pop-to-ecx
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+test-parse-decimal-int-from-slice-single-digit:
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    # (eax..ecx) = "3"
+    b8/copy-to-eax "3"/imm32
+    8b/-> *eax 1/r32/ecx
+    8d/copy-address *(eax+ecx+4) 1/r32/ecx
+    05/add-to-eax 4/imm32
+    # var slice/ecx: slice = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/<- %ecx 4/r32/esp
+    #
+    (parse-decimal-int-from-slice %ecx)  # => eax
+    (check-ints-equal %eax 3 "F - test-parse-decimal-int-from-slice-single-digit")
+$test-parse-decimal-int-helper-single-digit:end:
+    # . restore registers
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+test-parse-decimal-int-from-slice-multi-digit:
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    # (eax..ecx) = "34"
+    b8/copy-to-eax "34"/imm32
+    8b/-> *eax 1/r32/ecx
+    8d/copy-address *(eax+ecx+4) 1/r32/ecx
+    05/add-to-eax 4/imm32
+    # var slice/ecx: slice = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/<- %ecx 4/r32/esp
+    #
+    (parse-decimal-int-from-slice %ecx)  # => eax
+    (check-ints-equal %eax 0x22 "F - test-parse-decimal-int-from-slice-multi-digit")  # 34 in hex
+$test-parse-decimal-int-helper-multi-digit:end:
+    # . restore registers
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+test-parse-decimal-int-from-slice-zero:
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    # (eax..ecx) = "00"
+    b8/copy-to-eax "00"/imm32
+    8b/-> *eax 1/r32/ecx
+    8d/copy-address *(eax+ecx+4) 1/r32/ecx
+    05/add-to-eax 4/imm32
+    # var slice/ecx: slice = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/<- %ecx 4/r32/esp
+    #
+    (parse-decimal-int-from-slice %ecx)  # => eax
+    (check-ints-equal %eax 0 "F - test-parse-decimal-int-from-slice-zero")
+$test-parse-decimal-int-helper-zero:end:
+    # . restore registers
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+test-parse-decimal-int-from-slice-negative:
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    # (eax..ecx) = "-3"
+    b8/copy-to-eax "-3"/imm32
+    8b/-> *eax 1/r32/ecx
+    8d/copy-address *(eax+ecx+4) 1/r32/ecx
+    05/add-to-eax 4/imm32
+    # var slice/ecx: slice = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/<- %ecx 4/r32/esp
+    #
+    (parse-decimal-int-from-slice %ecx)  # => eax
+    (check-ints-equal %eax -3 "F - test-parse-decimal-int-from-slice-negative")
+$test-parse-decimal-int-helper-negative:end:
+    # . restore registers
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
+
+test-parse-decimal-int-from-slice-multi-digit-negative:
+    # . prologue
+    55/push-ebp
+    89/<- %ebp 4/r32/esp
+    # . save registers
+    50/push-eax
+    51/push-ecx
+    # (eax..ecx) = "-32"
+    b8/copy-to-eax "-32"/imm32
+    8b/-> *eax 1/r32/ecx
+    8d/copy-address *(eax+ecx+4) 1/r32/ecx
+    05/add-to-eax 4/imm32
+    # var slice/ecx: slice = {eax, ecx}
+    51/push-ecx
+    50/push-eax
+    89/<- %ecx 4/r32/esp
+    #
+    (parse-decimal-int-from-slice %ecx)  # => eax
+    (check-ints-equal %eax -0x20 "F - test-parse-decimal-int-from-slice-multi-digit-negative")  # -32 in hex
+$test-parse-decimal-int-helper-multi-digit-negative:end:
+    # . restore registers
+    59/pop-to-ecx
+    58/pop-to-eax
+    # . epilogue
+    89/<- %esp 5/r32/ebp
+    5d/pop-to-ebp
+    c3/return
diff --git a/400.mu b/400.mu
index 57032f0b..7b32f3cb 100644
--- a/400.mu
+++ b/400.mu
@@ -69,6 +69,8 @@ sig parse-hex-int-from-slice in: (addr slice) -> result/eax: int
 #sig parse-hex-int-helper start: (addr byte), end: (addr byte) -> result/eax: int
 sig is-hex-digit? c: byte -> result/eax: boolean
 #sig from-hex-char in/eax: byte -> out/eax: nibble
+sig parse-decimal-int-from-slice in: (addr slice) -> result/eax: int
+#sig parse-decimal-int-helper start: (addr byte), end: (addr byte) -> result/eax: int
 sig error-byte ed: (addr exit-descriptor), out: (addr buffered-file), msg: (addr array byte), n: byte
 #sig allocate ad: (addr allocation-descriptor), n: int, out: (addr handle _)
 #sig allocate-raw ad: (addr allocation-descriptor), n: int, out: (addr handle _)
@@ -127,10 +129,10 @@ sig compute-width-of-slice s: (addr slice) -> result/eax: int
 sig emit-hex-array out: (addr buffered-file), arr: (addr array byte)
 sig next-word-or-string line: (addr stream byte), out: (addr slice)
 sig write-int out: (addr stream byte), n: int
-sig clear-stack s: (addr stack)
-sig push s: (addr stack), n: int
-sig pop s: (addr stack) -> n/eax: int
-sig top s: (addr stack) -> n/eax: int
+#sig clear-stack s: (addr stack)
+#sig push s: (addr stack), n: int
+#sig pop s: (addr stack) -> n/eax: int
+#sig top s: (addr stack) -> n/eax: int
 sig array-equal? a: (addr array int), b: (addr array int) -> result/eax: boolean
 sig parse-array-of-ints ad: (addr allocation-descriptor), s: (addr string), out: (addr handle array int)
 sig check-array-equal a: (addr array int), expected: (addr string), msg: (addr string)
@@ -143,6 +145,7 @@ sig real-screen-size -> nrows/eax: int, ncols/ecx: int
 sig clear-real-screen
 sig move-cursor-on-real-screen row: int, column: int
 sig print-string-to-real-screen s: (addr array byte)
+sig print-slice-to-real-screen s: (addr slice)
 sig print-stream-to-real-screen s: (addr stream byte)
 sig print-grapheme-to-real-screen c: grapheme
 sig print-int32-hex-to-real-screen n: int
@@ -159,6 +162,7 @@ sig show-cursor-on-real-screen
 sig enable-keyboard-immediate-mode
 sig enable-keyboard-type-mode
 sig read-key-from-real-keyboard -> result/eax: byte
+sig read-line-from-real-keyboard in: (addr stream byte)
 sig open filename: (addr array byte), write?: boolean, out: (addr handle buffered-file)
 sig populate-buffered-file-containing contents: (addr array byte), out: (addr handle buffered-file)
 sig new-buffered-file out: (addr handle buffered-file)
diff --git a/apps/assort b/apps/assort
index 42c0c4d5..91acd903 100755
--- a/apps/assort
+++ b/apps/assort
Binary files differdiff --git a/apps/braces b/apps/braces
index fefabcc8..04c119a4 100755
--- a/apps/braces
+++ b/apps/braces
Binary files differdiff --git a/apps/calls b/apps/calls
index 443dc7f3..fcf9fa3e 100755
--- a/apps/calls
+++ b/apps/calls
Binary files differdiff --git a/apps/crenshaw2-1 b/apps/crenshaw2-1
index f26dedce..b47821fc 100755
--- a/apps/crenshaw2-1
+++ b/apps/crenshaw2-1
Binary files differdiff --git a/apps/crenshaw2-1b b/apps/crenshaw2-1b
index 139327ca..59603ae2 100755
--- a/apps/crenshaw2-1b
+++ b/apps/crenshaw2-1b
Binary files differdiff --git a/apps/dquotes b/apps/dquotes
index 302c3490..fe2b9da5 100755
--- a/apps/dquotes
+++ b/apps/dquotes
Binary files differdiff --git a/apps/factorial b/apps/factorial
index 7e8edb63..424b6a84 100755
--- a/apps/factorial
+++ b/apps/factorial
Binary files differdiff --git a/apps/hex b/apps/hex
index 75edad2d..5087ab7d 100755
--- a/apps/hex
+++ b/apps/hex
Binary files differdiff --git a/apps/mu b/apps/mu
index 3ebe2ab7..8acd93f9 100755
--- a/apps/mu
+++ b/apps/mu
Binary files differdiff --git a/apps/mu.subx b/apps/mu.subx
index 1b68350f..b3cb99de 100644
--- a/apps/mu.subx
+++ b/apps/mu.subx
@@ -14518,6 +14518,13 @@ size-of-type-id:  # t: type-id -> result/eax: int
       b8/copy-to-eax 8/imm32
       eb/jump $size-of-type-id:end/disp8  # eax changes type from type-id to int
     }
+    # if t is a slice, return 8
+    3d/compare-eax-and 0xc/imm32/slice
+    {
+      75/jump-if-!= break/disp8
+      b8/copy-to-eax 8/imm32
+      eb/jump $size-of-type-id:end/disp8  # eax changes type from type-id to int
+    }
     # if t is a user-defined type, return its size
     # TODO: support non-atom type
     (find-typeinfo %eax %ecx)
diff --git a/apps/pack b/apps/pack
index a6211a7d..ebae4f3a 100755
--- a/apps/pack
+++ b/apps/pack
Binary files differdiff --git a/apps/rpn.mu b/apps/rpn.mu
new file mode 100644
index 00000000..5e2fdbb6
--- /dev/null
+++ b/apps/rpn.mu
@@ -0,0 +1,149 @@
+# Integer arithmetic using postfix notation
+#
+# Limitations:
+#   No division yet.
+#
+# To build:
+#   $ ./translate_mu apps/rpn.mu
+#
+# Example session:
+#   $ ./a.elf
+#   press ctrl-c or ctrl-d to exit
+#   > 1
+#   1
+#   > 1 1 +
+#   2
+#   > 1 2 3 + +
+#   6
+#   > 1 2 3 * +
+#   7
+#   > 1 2 + 3 *
+#   9
+#   > 1 3 4 * +
+#   13
+#   > ^D
+#   $
+#
+# Error handling is non-existent. This is just a prototype.
+
+fn main -> exit-status/ebx: int {
+  var in-storage: (stream byte 0x100)
+  var in/esi: (addr stream byte) <- address in-storage
+  print-string 0, "press ctrl-c or ctrl-d to exit\n"
+  # read-eval-print loop
+  {
+    # print prompt
+    print-string 0, "> "
+    # read line
+    clear-stream in
+    read-line-from-real-keyboard in
+    var done?/eax: boolean <- stream-empty? in
+    compare done?, 0
+    break-if-!=
+    # parse and eval
+    var out/eax: int <- simplify in
+    # print
+    print-int32-decimal 0, out
+    print-string 0, "\n"
+    #
+    loop
+  }
+  exit-status <- copy 0
+}
+
+type int-stack {
+  data: (handle array int)
+  top: int
+}
+
+fn simplify in: (addr stream byte) -> result/eax: int {
+  var word-storage: slice
+  var word/ecx: (addr slice) <- address word-storage
+  var stack-storage: int-stack
+  var stack/esi: (addr int-stack) <- address stack-storage
+  initialize-stack stack, 0x10
+  $simplify:word-loop: {
+    next-word in, word
+    var done?/eax: boolean <- slice-empty? word
+    compare done?, 0
+    break-if-!=
+    # if word is an operator, perform it
+    {
+      var is-add?/eax: boolean <- slice-equal? word, "+"
+      compare is-add?, 0
+      break-if-=
+      var _b/eax: int <- pop-int-stack stack
+      var b/edx: int <- copy _b
+      var a/eax: int <- pop-int-stack stack
+      a <- add b
+      push-int-stack stack, a
+      loop $simplify:word-loop
+    }
+    {
+      var is-sub?/eax: boolean <- slice-equal? word, "-"
+      compare is-sub?, 0
+      break-if-=
+      var _b/eax: int <- pop-int-stack stack
+      var b/edx: int <- copy _b
+      var a/eax: int <- pop-int-stack stack
+      a <- subtract b
+      push-int-stack stack, a
+      loop $simplify:word-loop
+    }
+    {
+      var is-mul?/eax: boolean <- slice-equal? word, "*"
+      compare is-mul?, 0
+      break-if-=
+      var _b/eax: int <- pop-int-stack stack
+      var b/edx: int <- copy _b
+      var a/eax: int <- pop-int-stack stack
+      a <- multiply b
+      push-int-stack stack, a
+      loop $simplify:word-loop
+    }
+    # otherwise it's an int
+    var n/eax: int <- parse-decimal-int-from-slice word
+    push-int-stack stack, n
+    loop
+  }
+  result <- pop-int-stack stack
+}
+
+fn initialize-stack _self: (addr int-stack), n: int {
+  var self/esi: (addr int-stack) <- copy _self
+  var d/edi: (addr handle array int) <- get self, data
+  populate d, n
+  var top/eax: (addr int) <- get self, top
+  copy-to *top, 0
+}
+
+fn push-int-stack _self: (addr int-stack), _val: int {
+  var self/esi: (addr int-stack) <- copy _self
+  var top-addr/ecx: (addr int) <- get self, top
+  var data-ah/edx: (addr handle array int) <- get self, data
+  var data/eax: (addr array int) <- lookup *data-ah
+  var top/edx: int <- copy *top-addr
+  var dest-addr/edx: (addr int) <- index data, top
+  var val/eax: int <- copy _val
+  copy-to *dest-addr, val
+  add-to *top-addr, 1
+}
+
+fn pop-int-stack _self: (addr int-stack) -> val/eax: int {
+$pop-int-stack:body: {
+  var self/esi: (addr int-stack) <- copy _self
+  var top-addr/ecx: (addr int) <- get self, top
+  {
+    compare *top-addr, 0
+    break-if->
+    val <- copy 0
+    break $pop-int-stack:body
+  }
+  subtract-from *top-addr, 1
+  var data-ah/edx: (addr handle array int) <- get self, data
+  var data/eax: (addr array int) <- lookup *data-ah
+  var top/edx: int <- copy *top-addr
+  var result-addr/eax: (addr int) <- index data, top
+  val <- copy *result-addr
+}
+}
diff --git a/apps/sigils b/apps/sigils
index 82f4e43a..087caf38 100755
--- a/apps/sigils
+++ b/apps/sigils
Binary files differdiff --git a/apps/survey b/apps/survey
index abb0d1f0..76cadf48 100755
--- a/apps/survey
+++ b/apps/survey
Binary files differdiff --git a/apps/tests b/apps/tests
index 7a4df46d..8c013997 100755
--- a/apps/tests
+++ b/apps/tests
Binary files differdiff --git a/apps/tests.subx b/apps/tests.subx
index 75f8ef22..d61ea137 100644
--- a/apps/tests.subx
+++ b/apps/tests.subx
@@ -80,6 +80,8 @@ subx-gen-run-tests:  # in: (addr buffered-file), out: (addr buffered-file)
     #     read-line-buffered(in, line)
     #     if (line->write == 0) break               # end of file
     #     var word-slice = next-word(line)
+    #     if slice-empty?(word-slice)               # empty line
+    #       continue
     #     if is-label?(word-slice)
     #       if slice-starts-with?(word-slice, "test-")
     #         tests-found = true
@@ -184,6 +186,16 @@ $subx-gen-run-tests:check0:
     e8/call  next-word/disp32
     # . . discard args
     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
+$subx-gen-run-tests:check-empty:
+    # if slice-empty?(word-slice) break
+    # . eax = slice-empty?(word-slice)
+    52/push-edx
+    e8/call  slice-empty?/disp32
+    # . . discard args
+    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
+    # . if (eax != false) break
+    3d/compare-eax-and  0/imm32/false
+    75/jump-if-!=  $subx-gen-run-tests:loop/disp8
 $subx-gen-run-tests:check-for-label:
     # if (!is-label?(word-slice)) continue
     # . eax = is-label?(word-slice)