about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--mu-init.subx32
-rw-r--r--shell/infix.mu531
-rw-r--r--shell/macroexpand.mu4
-rw-r--r--shell/parse.mu10
-rw-r--r--shell/primitives.mu2
-rw-r--r--shell/print.mu4
6 files changed, 557 insertions, 26 deletions
diff --git a/mu-init.subx b/mu-init.subx
index 438e29e7..26accb8a 100644
--- a/mu-init.subx
+++ b/mu-init.subx
@@ -14,23 +14,21 @@ Entry:
   bd/copy-to-ebp 0/imm32
   #
 #?   (main 0 0 Primary-bus-secondary-drive)
-#?   (set-cursor-position 0 0x40 0x20)
-#?   (test-parenthesize)
-#?   (test-parenthesize-skips-lines-with-initial-parens)
-#?   (test-parenthesize-skips-single-word-lines)
-  # always first run tests
-  (run-tests)
-  (num-test-failures)  # => eax
-  # call main if tests all passed
-  {
-    3d/compare-eax-and 0/imm32
-    75/jump-if-!= break/disp8
-    c7 0/subop/copy *Running-tests? 0/imm32/false
-    (clear-real-screen)
-    c7 0/subop/copy *Real-screen-cursor-x 0/imm32
-    c7 0/subop/copy *Real-screen-cursor-y 0/imm32
-    (main 0 0 Primary-bus-secondary-drive)
-  }
+  (set-cursor-position 0 0x30 2)
+  (test-infix)
+#?   # always first run tests
+#?   (run-tests)
+#?   (num-test-failures)  # => eax
+#?   # call main if tests all passed
+#?   {
+#?     3d/compare-eax-and 0/imm32
+#?     75/jump-if-!= break/disp8
+#?     c7 0/subop/copy *Running-tests? 0/imm32/false
+#?     (clear-real-screen)
+#?     c7 0/subop/copy *Real-screen-cursor-x 0/imm32
+#?     c7 0/subop/copy *Real-screen-cursor-y 0/imm32
+#?     (main 0 0 Primary-bus-secondary-drive)
+#?   }
 
   # hang indefinitely
   {
diff --git a/shell/infix.mu b/shell/infix.mu
index 0d23b3f4..c0b37cb9 100644
--- a/shell/infix.mu
+++ b/shell/infix.mu
@@ -1,6 +1,533 @@
-fn transform-infix _x-ah: (addr handle cell), trace: (addr trace) {
+fn transform-infix x-ah: (addr handle cell), trace: (addr trace) {
   trace-text trace, "infix", "transform infix"
   trace-lower trace
-  trace-text trace, "infix", "todo"
+#?   draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "a:", 2/fg 0/bg
+#?   dump-cell-from-cursor-over-full-screen x-ah, 7/fg 0/bg
+  transform-infix-2 x-ah, trace
   trace-higher trace
 }
+
+# Break any symbols containing operators down in place into s-expressions
+# Transform (... sym op sym ...) greedily in place into (... (op sym sym) ...)
+# Lisp code typed in at the keyboard will never have cycles
+fn transform-infix-2 _x-ah: (addr handle cell), trace: (addr trace) {
+  var x-ah/edi: (addr handle cell) <- copy _x-ah
+  var x/eax: (addr cell) <- lookup *x-ah
+  # trace x-ah {{{
+  {
+    var should-trace?/eax: boolean <- should-trace? trace
+    compare should-trace?, 0/false
+    break-if-=
+    var stream-storage: (stream byte 0x300)
+    var stream/ecx: (addr stream byte) <- address stream-storage
+    var nested-trace-storage: trace
+    var nested-trace/esi: (addr trace) <- address nested-trace-storage
+    initialize-trace nested-trace, 1/only-errors, 0x10/capacity, 0/visible
+    print-cell x-ah, stream, nested-trace
+    trace trace, "infix", stream
+  }
+  # }}}
+  trace-lower trace
+#?   {
+#?     var foo/eax: int <- copy x
+#?     draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, foo, 7/fg 0/bg
+#?   }
+#?   dump-cell-from-cursor-over-full-screen x-ah, 5/fg 0/bg
+  # null? return
+  compare x, 0
+  {
+    break-if-!=
+    trace-higher trace
+    trace-text trace, "infix", "=> NULL"
+    return
+  }
+  # nil? return
+  {
+    var nil?/eax: boolean <- nil? x
+    compare nil?, 0/false
+    break-if-=
+    trace-higher trace
+    trace-text trace, "infix", "=> nil"
+    return
+  }
+  var x-type/ecx: (addr int) <- get x, type
+  # symbol? maybe break it down into a pair
+  {
+    compare *x-type, 2/symbol
+    break-if-=
+    tokenize-infix x-ah, trace
+  }
+  # not a pair? return
+#?   draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "a", 4/fg 0/bg
+#?   draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, *x-type, 5/fg 0/bg
+  {
+    compare *x-type, 0/pair
+    break-if-=
+    trace-higher trace
+    # trace "=> " x-ah {{{
+    {
+      var should-trace?/eax: boolean <- should-trace? trace
+      compare should-trace?, 0/false
+      break-if-=
+      var stream-storage: (stream byte 0x300)
+      var stream/ecx: (addr stream byte) <- address stream-storage
+      write stream, "=> "
+      var nested-trace-storage: trace
+      var nested-trace/esi: (addr trace) <- address nested-trace-storage
+      initialize-trace nested-trace, 1/only-errors, 0x10/capacity, 0/visible
+      print-cell x-ah, stream, nested-trace
+      trace trace, "infix", stream
+    }
+    # }}}
+#?     draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "^", 4/fg 0/bg
+    return
+  }
+#?   draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "b", 4/fg 0/bg
+  # singleton operator? unwrap
+  {
+    var first-ah/ecx: (addr handle cell) <- get x, left
+    {
+      var first/eax: (addr cell) <- lookup *first-ah
+      var infix?/eax: boolean <- operator-symbol? first
+      compare infix?, 0/false
+    }
+    break-if-=
+    var rest-ah/eax: (addr handle cell) <- get x, right
+    var rest/eax: (addr cell) <- lookup *rest-ah
+    var rest-nil?/eax: boolean <- nil? rest
+    compare rest-nil?, 0/false
+    break-if-=
+    copy-object first-ah, x-ah
+    trace-higher trace
+    # trace "=> " x-ah {{{
+    {
+      var should-trace?/eax: boolean <- should-trace? trace
+      compare should-trace?, 0/false
+      break-if-=
+      var stream-storage: (stream byte 0x300)
+      var stream/ecx: (addr stream byte) <- address stream-storage
+      write stream, "=> "
+      var nested-trace-storage: trace
+      var nested-trace/esi: (addr trace) <- address nested-trace-storage
+      initialize-trace nested-trace, 1/only-errors, 0x10/capacity, 0/visible
+      print-cell x-ah, stream, nested-trace
+      trace trace, "infix", stream
+    }
+    # }}}
+    return
+  }
+#?   draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "c", 4/fg 0/bg
+  # non-singleton pair. try to "pinch out" infix pattern at root
+  $transform-infix-2:pinch: {
+    # scan past first three elements
+    var first-ah/ecx: (addr handle cell) <- get x, left
+    var rest-ah/esi: (addr handle cell) <- get x, right
+    var rest/eax: (addr cell) <- lookup *rest-ah
+    compare rest, 0
+    break-if-=
+#?     draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "d", 4/fg 0/bg
+    var second-ah/edx: (addr handle cell) <- get rest, left
+    rest-ah <- get rest, right
+    var rest/eax: (addr cell) <- lookup *rest-ah
+    compare rest, 0
+    break-if-=
+#?     draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "e", 4/fg 0/bg
+    var third-ah/ebx: (addr handle cell) <- get rest, left
+    rest-ah <- get rest, right
+    # if second is not an operator, break
+    {
+      var second/eax: (addr cell) <- lookup *second-ah
+      var infix?/eax: boolean <- operator-symbol? second
+      compare infix?, 0/false
+    }
+    break-if-=
+#?     draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "f", 4/fg 0/bg
+    # swap the top 2
+    swap-cells first-ah, second-ah
+    # if there's more than three elements, perform a more complex 'rotation'
+    rest <- lookup *rest-ah
+    var rest-nil?/eax: boolean <- nil? rest
+    compare rest-nil?, 0/false
+    break-if-!=
+#?     draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "g", 4/fg 0/bg
+    # save and clear third->right
+    var saved-rest-h: (handle cell)
+    var saved-rest-ah/eax: (addr handle cell) <- address saved-rest-h
+    copy-object rest-ah, saved-rest-ah
+    nil rest-ah
+    # create new-node out of first..third and rest
+    var result-h: (handle cell)
+    var result-ah/eax: (addr handle cell) <- address result-h
+    new-pair result-ah, *x-ah, saved-rest-h
+    # save
+    copy-object result-ah, x-ah
+  }
+  # recurse after any pinching
+  var x/eax: (addr cell) <- lookup *x-ah  # refresh
+#?   dump-cell-from-cursor-over-full-screen x-ah, 1/fg 0/bg
+  var left-ah/ecx: (addr handle cell) <- get x, left
+#?   draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "x", 1/fg 0/bg
+#?   dump-cell-from-cursor-over-full-screen left-ah, 2/fg 0/bg
+  transform-infix-2 left-ah, trace
+  var right-ah/ecx: (addr handle cell) <- get x, right
+#?   draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "y", 1/fg 0/bg
+#?   dump-cell-from-cursor-over-full-screen right-ah, 3/fg 0/bg
+  transform-infix-2 right-ah, trace
+#?   draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "z", 1/fg 0/bg
+  trace-higher trace
+    # trace "=> " x-ah {{{
+    {
+      var should-trace?/eax: boolean <- should-trace? trace
+      compare should-trace?, 0/false
+      break-if-=
+      var stream-storage: (stream byte 0x300)
+      var stream/ecx: (addr stream byte) <- address stream-storage
+      write stream, "=> "
+      var nested-trace-storage: trace
+      var nested-trace/esi: (addr trace) <- address nested-trace-storage
+      initialize-trace nested-trace, 1/only-errors, 0x10/capacity, 0/visible
+      print-cell x-ah, stream, nested-trace
+      trace trace, "infix", stream
+    }
+    # }}}
+}
+
+fn swap-cells a-ah: (addr handle cell), b-ah: (addr handle cell) {
+  var tmp-h: (handle cell)
+  var tmp-ah/eax: (addr handle cell) <- address tmp-h
+  copy-object a-ah, tmp-ah
+  copy-object b-ah, a-ah
+  copy-object tmp-ah, b-ah
+}
+
+fn tokenize-infix _sym-ah: (addr handle cell), trace: (addr trace) {
+#?     # non-symbol operators
+#?     {
+#?       var operator?/eax: boolean <- operator-grapheme? g
+#?       compare operator?, 0/false
+#?       break-if-=
+#?       next-operator-token in, out, trace
+#?       break $next-token:case
+#?     }
+}
+
+fn test-infix {
+  check-infix "abc", "abc", "F - test-infix/regular-symbol"
+  check-infix "-3", "-3", "F - test-infix/negative-integer-literal"
+  check-infix "[a b+c]", "[a b+c]", "F - test-infix/string-literal"
+  check-infix "$a", "$a", "F - test-infix/dollar-var"
+  check-infix "$+", "$+", "F - test-infix/dollar-operator"
+  check-infix "(+)", "+", "F - test-infix/operator-without-args"
+  check-infix "(= (+) 3)", "(= + 3)", "F - test-infix/operator-without-args-2"
+  check-infix "($+)", "$+", "F - test-infix/dollar-operator-without-args"
+  check-infix "',(a + b)", "',(+ a b)", "F - test-infix/nested-quotes"
+#?   check-infix "',(+)", "',+", "F - test-infix/nested-quotes-2"
+  check-infix "(a + b)", "(+ a b)", "F - test-infix/simple-list"
+  check-infix "(a (+) b)", "(a + b)", "F - test-infix/wrapped-operator"
+  check-infix "(+ a b)", "(+ a b)", "F - test-infix/prefix-operator"
+  check-infix "(a . b)", "(a . b)", "F - test-infix/dot-operator"
+  check-infix "(a b . c)", "(a b . c)", "F - test-infix/dotted-list"
+  check-infix "(+ . b)", "(+ . b)", "F - test-infix/dotted-list-with-operator"
+  check-infix "(+ a)", "(+ a)", "F - test-infix/unary-operator"
+  check-infix "((a + b))", "((+ a b))", "F - test-infix/nested-list"
+  check-infix "(do (a + b))", "(do (+ a b))", "F - test-infix/nested-list-2"
+  check-infix "(a = (a + 1))", "(= a (+ a 1))", "F - test-infix/nested-list-3"
+#?   check-infix "(a + b + c)", "(+ (+ a b) c)", "F - test-infix/left-associative"
+#?   check-infix "(f a + b)", "(f (+ a b))", "F - test-infix/higher-precedence-than-call"
+#?   check-infix "(f a + b c + d)", "(f (+ a b) (+ c d))", "F - test-infix/multiple"
+#?   check-infix "+a", "(+ a)", "F - test-infix/unary-operator-2"
+#?   check-infix "-a", "(- a)", "F - test-infix/unary-operator-3"
+#?   check-infix "a+b", "(+ a b)", "F - test-infix/no-spaces"
+#?   check-infix "',a+b", "',(+ a b)", "F - test-infix/no-spaces-with-nested-quotes"
+#?   check-infix "$a+b", "(+ $a b)", "F - test-infix/no-spaces-2"
+#?   check-infix "-a+b", "(+ (- a) b)", "F - test-infix/unary-over-binary"
+#?   check-infix "~a+b", "(+ (~ a) b)", "F - test-infix/unary-complement"
+#?   check-infix "(n * n-1)", "(* n (- n 1))", "F - test-infix/no-spaces-over-spaces"
+#?   check-infix "`(a + b)", "`(+ a b)", "F - test-infix/backquote"
+#?   check-infix ",@a+b", ",@(+ a b)", "F - test-infix/unquote-splice"
+#?   check-infix ",@(a + b)", ",@(+ a b)", "F - test-infix/unquote-splice-2"
+}
+
+# helpers
+
+# assumes symbol? is already fully tokenized,
+# consists entirely of either operator or non-operator graphemes
+fn operator-symbol? _x: (addr cell) -> _/eax: boolean {
+  var x/esi: (addr cell) <- copy _x
+  {
+    var x-type/eax: (addr int) <- get x, type
+    compare *x-type, 2/symbol
+    break-if-=
+    return 0/false
+  }
+  var x-data-ah/eax: (addr handle stream byte) <- get x, text-data
+  var _x-data/eax: (addr stream byte) <- lookup *x-data-ah
+  var x-data/esi: (addr stream byte) <- copy _x-data
+  rewind-stream x-data
+  var g/eax: grapheme <- read-grapheme x-data
+  # special case: '$' is reserved for gensyms, and can work with either
+  # operator or non-operator symbols.
+  {
+    compare g, 0x24/dollar
+    break-if-!=
+    {
+      var all-dollars?/eax: boolean <- stream-empty? x-data
+      compare all-dollars?, 0/false
+      break-if-=
+      # '$', '$$', '$$$', etc. are regular symbols
+      return 0/false
+    }
+    g <- read-grapheme x-data
+    loop
+  }
+  var result/eax: boolean <- operator-grapheme? g
+  return result
+}
+
+fn non-operator-grapheme? g: grapheme -> _/eax: boolean {
+  ## whitespace
+  compare g, 9/tab
+  {
+    break-if-!=
+    return 0/false
+  }
+  compare g, 0xa/newline
+  {
+    break-if-!=
+    return 0/false
+  }
+  compare g, 0x20/space
+  {
+    break-if-!=
+    return 0/false
+  }
+  ## we don't really use double quotes
+  compare g, 0x22/double-quote
+  {
+    break-if-!=
+    return 1/true
+  }
+  ## brackets
+  compare g, 0x28/open-paren
+  {
+    break-if-!=
+    return 0/false
+  }
+  compare g, 0x29/close-paren
+  {
+    break-if-!=
+    return 0/false
+  }
+  compare g, 0x5b/open-square-bracket
+  {
+    break-if-!=
+    return 0/false
+  }
+  compare g, 0x5d/close-square-bracket
+  {
+    break-if-!=
+    return 0/false
+  }
+  compare g, 0x7b/open-curly-bracket
+  {
+    break-if-!=
+    return 0/false
+  }
+  compare g, 0x7d/close-curly-bracket
+  {
+    break-if-!=
+    return 0/false
+  }
+  # quotes and unquotes are like symbols for this purpose
+  compare g, 0x27/single-quote
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x60/backquote
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x2c/comma
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x40/at-sign
+  {
+    break-if-!=
+    return 1/true
+  }
+  # - other punctuation
+  compare g, 0x23/hash
+  {
+    break-if-!=
+    return 0/false
+  }
+  return 1/true
+}
+
+fn operator-grapheme? g: grapheme -> _/eax: boolean {
+  # '$' is a symbol char
+  compare g, 0x25/percent
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x26/ampersand
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x27/single-quote
+  {
+    break-if-!=
+    return 0/false
+  }
+  compare g, 0x60/backquote
+  {
+    break-if-!=
+    return 0/false
+  }
+  compare g, 0x2c/comma
+  {
+    break-if-!=
+    return 0/false
+  }
+  compare g, 0x40/at-sign
+  {
+    break-if-!=
+    return 0/false
+  }
+  compare g, 0x2a/asterisk
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x2b/plus
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x2d/dash  # '-' not allowed in symbols
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x2e/period
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x2f/slash
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x3a/colon
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x3b/semi-colon
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x3c/less-than
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x3d/equal
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x3e/greater-than
+  {
+    break-if-!=
+    return 1/true
+  }
+  # '?' is a symbol char
+  compare g, 0x5c/backslash
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x5e/caret
+  {
+    break-if-!=
+    return 1/true
+  }
+  # '_' is a symbol char
+  compare g, 0x7c/vertical-line
+  {
+    break-if-!=
+    return 1/true
+  }
+  compare g, 0x7e/tilde
+  {
+    break-if-!=
+    return 1/true
+  }
+  return 0/false
+}
+
+# helpers for tests
+
+fn check-infix actual: (addr array byte), expected: (addr array byte), message: (addr array byte) {
+  var trace-storage: trace
+  var trace/edx: (addr trace) <- address trace-storage
+#?   initialize-trace trace, 1/only-errors, 0x10/capacity, 0/visible
+  initialize-trace trace, 0x10/levels, 0x1000/capacity, 0/visible
+  #
+  var actual-buffer-storage: gap-buffer
+  var actual-buffer/eax: (addr gap-buffer) <- address actual-buffer-storage
+  initialize-gap-buffer-with actual-buffer, actual
+  var actual-tree-h: (handle cell)
+  var actual-tree-ah/esi: (addr handle cell) <- address actual-tree-h
+  read-cell actual-buffer, actual-tree-ah, trace
+#?   dump-trace-with-label trace, "infix"
+  dump-cell-from-cursor-over-full-screen actual-tree-ah, 7/fg 0/bg
+  var _actual-tree/eax: (addr cell) <- lookup *actual-tree-ah
+  var actual-tree/esi: (addr cell) <- copy _actual-tree
+  #
+  var expected-buffer-storage: gap-buffer
+  var expected-buffer/eax: (addr gap-buffer) <- address expected-buffer-storage
+  initialize-gap-buffer-with expected-buffer, expected
+  var expected-tree-h: (handle cell)
+  var expected-tree-ah/edi: (addr handle cell) <- address expected-tree-h
+  read-without-infix expected-buffer, expected-tree-ah, trace
+  var expected-tree/eax: (addr cell) <- lookup *expected-tree-ah
+  #
+  var match?/eax: boolean <- cell-isomorphic? actual-tree, expected-tree, trace
+  check match?, message
+}
+
+fn read-without-infix in: (addr gap-buffer), out: (addr handle cell), trace: (addr trace) {
+  # eagerly tokenize everything so that the phases are easier to see in the trace
+  var tokens-storage: (stream token 0x400)
+  var tokens/edx: (addr stream token) <- address tokens-storage
+  tokenize in, tokens, trace
+  var error?/eax: boolean <- has-errors? trace
+  compare error?, 0/false
+  {
+    break-if-=
+    dump-trace trace
+    return
+  }
+  # insert more parens based on indentation
+  var parenthesized-tokens-storage: (stream token 0x400)
+  var parenthesized-tokens/ecx: (addr stream token) <- address parenthesized-tokens-storage
+  parenthesize tokens, parenthesized-tokens, trace
+  var error?/eax: boolean <- has-errors? trace
+  compare error?, 0/false
+  {
+    break-if-=
+    dump-trace trace
+    return
+  }
+  parse-input parenthesized-tokens, out, trace
+}
diff --git a/shell/macroexpand.mu b/shell/macroexpand.mu
index 0f3837c7..74f0e9c5 100644
--- a/shell/macroexpand.mu
+++ b/shell/macroexpand.mu
@@ -600,7 +600,7 @@ fn pending-test-macroexpand-inside-nested-backquote-unquote {
   var dummy/eax: boolean <- macroexpand-iter result-ah, globals, trace
   var error?/eax: boolean <- has-errors? trace
   check-not error?, "F - test-macroexpand-inside-nested-backquote-unquote/error"
-  dump-cell-from-cursor-over-full-screen result-ah
+#?   dump-cell-from-cursor-over-full-screen result-ah
   var _result/eax: (addr cell) <- lookup *result-ah
   var result/edi: (addr cell) <- copy _result
   # expected
@@ -610,7 +610,7 @@ fn pending-test-macroexpand-inside-nested-backquote-unquote {
   var expected-h: (handle cell)
   var expected-ah/edx: (addr handle cell) <- address expected-h
   read-cell expected-gap, expected-ah, trace
-  dump-cell-from-cursor-over-full-screen expected-ah
+#?   dump-cell-from-cursor-over-full-screen expected-ah
   var expected/eax: (addr cell) <- lookup *expected-ah
   #
   var assertion/eax: boolean <- cell-isomorphic? result, expected, trace
diff --git a/shell/parse.mu b/shell/parse.mu
index c9e2901f..d888f56b 100644
--- a/shell/parse.mu
+++ b/shell/parse.mu
@@ -8,8 +8,8 @@ fn parse-input tokens: (addr stream token), out: (addr handle cell), trace: (add
     return
   }
   var close-paren?/eax: boolean <- copy 0/false
-  var dummy?/ecx: boolean <- copy 0/false
-  close-paren?, dummy? <- parse-sexpression tokens, out, trace
+  var dot?/ecx: boolean <- copy 0/false
+  close-paren?, dot? <- parse-sexpression tokens, out, trace
   {
     compare close-paren?, 0/false
     break-if-=
@@ -17,6 +17,12 @@ fn parse-input tokens: (addr stream token), out: (addr handle cell), trace: (add
     return
   }
   {
+    compare dot?, 0/false
+    break-if-=
+    error trace, "'.' is not a valid expression"
+    return
+  }
+  {
     var empty?/eax: boolean <- stream-empty? tokens
     compare empty?, 0/false
     break-if-!=
diff --git a/shell/primitives.mu b/shell/primitives.mu
index 1b25583c..44852c8a 100644
--- a/shell/primitives.mu
+++ b/shell/primitives.mu
@@ -1135,7 +1135,7 @@ fn apply-debug _args-ah: (addr handle cell), out: (addr handle cell), trace: (ad
   }
   # dump args->left uglily to screen and wait for a keypress
   var first-ah/eax: (addr handle cell) <- get args, left
-  dump-cell-from-cursor-over-full-screen first-ah
+  dump-cell-from-cursor-over-full-screen first-ah, 7/fg 0/bg
   {
     var foo/eax: byte <- read-key 0/keyboard
     compare foo, 0
diff --git a/shell/print.mu b/shell/print.mu
index ea5db3cf..1f652fa5 100644
--- a/shell/print.mu
+++ b/shell/print.mu
@@ -133,14 +133,14 @@ fn dump-cell-at-top-right in-ah: (addr handle cell) {
   d1, d2 <- draw-stream-wrapping-right-then-down 0/screen, stream, 0/xmin, 0/ymin, 0x80/xmax, 0x30/ymax, 0/x, 0/y, 7/fg, 0xc5/bg=blue-bg
 }
 
-fn dump-cell-from-cursor-over-full-screen in-ah: (addr handle cell) {
+fn dump-cell-from-cursor-over-full-screen in-ah: (addr handle cell), fg: int, bg: int {
   var stream-storage: (stream byte 0x200)
   var stream/edx: (addr stream byte) <- address stream-storage
   var trace-storage: trace
   var trace/edi: (addr trace) <- address trace-storage
   initialize-trace trace, 1/only-errors, 0x10/capacity, 0/visible
   print-cell in-ah, stream, trace
-  draw-stream-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, stream, 7/fg, 0/bg
+  draw-stream-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, stream, fg, bg
 }
 
 fn print-symbol _in: (addr cell), out: (addr stream byte), trace: (addr trace) {