about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--103glyph.subx (renamed from 103grapheme.subx)81
-rw-r--r--317abort.subx2
-rw-r--r--400.mu8
-rw-r--r--403unicode.mu15
-rw-r--r--500fake-screen.mu80
-rw-r--r--501draw-text.mu41
-rw-r--r--504test-screen.mu75
-rw-r--r--513grapheme-stack.mu27
-rw-r--r--514gap-buffer.mu4
-rw-r--r--apps/colors.mu4
-rw-r--r--apps/rpn.mu8
-rw-r--r--browse-slack/environment.mu41
-rw-r--r--linux/403unicode.mu7
-rw-r--r--shell/primitives.mu2
-rw-r--r--shell/sandbox.mu8
15 files changed, 210 insertions, 193 deletions
diff --git a/103grapheme.subx b/103glyph.subx
index abcfe57c..61035f22 100644
--- a/103grapheme.subx
+++ b/103glyph.subx
@@ -1,4 +1,9 @@
-# Use the built-in font to draw a grapheme to real screen.
+# Use the built-in font to draw glyphs to screen.
+#   https://en.wikipedia.org/wiki/Glyph#Typography
+# The Mu computer can currently only render glyphs corresponding to single
+# code points. No combining characters.
+#   https://en.wikipedia.org/wiki/Code_point
+#   https://en.wikipedia.org/wiki/Combining_character
 #
 # We need to do this in machine code because Mu doesn't have global variables
 # yet (for the start of the font).
@@ -9,20 +14,20 @@
 # The Mu computer's font is 8px wide and 16px tall.
 # Therefore 'x' here is in [0, 128), and 'y' is in [0, 48)
 # Doesn't update the cursor; where the cursor should go after printing the
-# current grapheme is a higher-level concern.
-draw-grapheme-on-real-screen:  # g: grapheme, x: int, y: int, color: int, background-color: int -> _/eax
+# current code-point is a higher-level concern.
+draw-code-point-on-real-screen:  # c: code-point, x: int, y: int, color: int, background-color: int -> _/eax
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
     #
-    (draw-grapheme-on-screen-buffer *Video-memory-addr *(ebp+8) *(ebp+0xc) *(ebp+0x10) *(ebp+0x14) *(ebp+0x18) 0x80 0x30)  # => eax
-$draw-grapheme-on-real-screen:end:
+    (draw-code-point-on-screen-buffer *Video-memory-addr *(ebp+8) *(ebp+0xc) *(ebp+0x10) *(ebp+0x14) *(ebp+0x18) 0x80 0x30)  # => eax
+$draw-code-point-on-real-screen:end:
     # . epilogue
     89/<- %esp 5/r32/ebp
     5d/pop-to-ebp
     c3/return
 
-draw-grapheme-on-screen-array:  # screen-data: (addr array byte), g: grapheme, x: int, y: int, color: int, background-color: int, screen-width: int, screen-height: int -> _/eax: int
+draw-code-point-on-screen-array:  # screen-data: (addr array byte), c: code-point, x: int, y: int, color: int, background-color: int, screen-width: int, screen-height: int -> _/eax: int
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -39,17 +44,17 @@ draw-grapheme-on-screen-array:  # screen-data: (addr array byte), g: grapheme, x
       8b/-> *(ebp+0x20) 0/r32/eax
       f7 4/subop/multiply-into-eax *(ebp+0x24)
       81 7/subop/compare %edx 0/imm32
-      0f 85/jump-if-!= $draw-grapheme-on-screen-array:overflow/disp32
+      0f 85/jump-if-!= $draw-code-point-on-screen-array:overflow/disp32
       # if (eax > ecx) abort
       39/compare %eax 1/r32/ecx
-      0f 8f/jump-if-> $draw-grapheme-on-screen-array:abort/disp32
+      0f 8f/jump-if-> $draw-code-point-on-screen-array:abort/disp32
     }
     # eax = screen-data+4   (skip length)
     8b/-> *(ebp+8) 0/r32/eax
     05/add-to-eax 4/imm32
     #
-    (draw-grapheme-on-screen-buffer %eax *(ebp+0xc) *(ebp+0x10) *(ebp+0x14) *(ebp+0x18) *(ebp+0x1c) *(ebp+0x20) *(ebp+0x24))  # => eax
-$draw-grapheme-on-screen-array:end:
+    (draw-code-point-on-screen-buffer %eax *(ebp+0xc) *(ebp+0x10) *(ebp+0x14) *(ebp+0x18) *(ebp+0x1c) *(ebp+0x20) *(ebp+0x24))  # => eax
+$draw-code-point-on-screen-array:end:
     # . restore registers
     5a/pop-to-edx
     59/pop-to-ecx
@@ -58,30 +63,30 @@ $draw-grapheme-on-screen-array:end:
     5d/pop-to-ebp
     c3/return
 
-$draw-grapheme-on-screen-array:overflow:
-    (abort "draw-grapheme-on-screen-array: screen dimensions too large")
+$draw-code-point-on-screen-array:overflow:
+    (abort "draw-code-point-on-screen-array: screen dimensions too large")
 
-$draw-grapheme-on-screen-array:abort:
-    (abort "draw-grapheme-on-screen-array: coordinates are off the screen. Are the screen dimensions correct?")
+$draw-code-point-on-screen-array:abort:
+    (abort "draw-code-point-on-screen-array: coordinates are off the screen. Are the screen dimensions correct?")
 
 # 'buffer' here is not a valid Mu type: a naked address without a length.
 # returns number of 8x16 units printed to screen (1 or 2).
-draw-grapheme-on-screen-buffer:  # buffer: (addr byte), g: grapheme, x: int, y: int, color: int, background-color: int, screen-width: int, screen-height: int -> _/eax: int
+draw-code-point-on-screen-buffer:  # buffer: (addr byte), c: code-point, x: int, y: int, color: int, background-color: int, screen-width: int, screen-height: int -> _/eax: int
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
     # . save registers
     56/push-esi
-    # switch screen-width and screen-height from grapheme to pixel units
+    # switch screen-width and screen-height from code-point to pixel units
     c1 4/subop/shift-left *(ebp+20) 3/imm8/log2-font-width
     c1 4/subop/shift-left *(ebp+24) 4/imm8/log2-font-height
-    # esi = g
+    # esi = c
     8b/-> *(ebp+0xc) 6/r32/esi
-    # if (g >= 4352) return  # unicode planes supported: latin, greek, cyrillic, armenian, hebrew, arabic, syriac, thaana, n'ko, indian (iscii), sinhala, thai, lao, tibetan, myanmar, georgian
+    # if (c >= 4352) return  # unicode planes supported: latin, greek, cyrillic, armenian, hebrew, arabic, syriac, thaana, n'ko, indian (iscii), sinhala, thai, lao, tibetan, myanmar, georgian
                              # next few to support: CJK, ethiopic, cherokee, ...
     81 7/subop/compare %esi 0x1100/imm32
-    0f 8d/jump-if->= $draw-grapheme-on-screen-buffer:end/disp32
-    # var letter-bitmap/esi = font[g]
+    0f 8d/jump-if->= $draw-code-point-on-screen-buffer:end/disp32
+    # var letter-bitmap/esi = font[c]
     69/multiply %esi 0x21/imm32/glyph-size 6/r32/esi
     81 0/subop/add %esi 0x0010000c/imm32/Font  # see boot.subx
     # dispatch based on letter-bitmap->size
@@ -91,13 +96,13 @@ draw-grapheme-on-screen-buffer:  # buffer: (addr byte), g: grapheme, x: int, y:
     3d/compare-eax-and 8/imm32
     {
       75/jump-if-!= break/disp8
-      (draw-narrow-grapheme-on-screen-buffer *(ebp+8) %esi *(ebp+0x10) *(ebp+0x14) *(ebp+0x18) *(ebp+0x1c) *(ebp+0x20) *(ebp+0x24))
+      (draw-narrow-code-point-on-screen-buffer *(ebp+8) %esi *(ebp+0x10) *(ebp+0x14) *(ebp+0x18) *(ebp+0x1c) *(ebp+0x20) *(ebp+0x24))
       b8/copy-to-eax 1/imm32
-      eb/jump $draw-grapheme-on-screen-buffer:end/disp8
+      eb/jump $draw-code-point-on-screen-buffer:end/disp8
     }
-    (draw-wide-grapheme-on-screen-buffer *(ebp+8) %esi *(ebp+0x10) *(ebp+0x14) *(ebp+0x18) *(ebp+0x1c) *(ebp+0x20) *(ebp+0x24))
+    (draw-wide-code-point-on-screen-buffer *(ebp+8) %esi *(ebp+0x10) *(ebp+0x14) *(ebp+0x18) *(ebp+0x1c) *(ebp+0x20) *(ebp+0x24))
     b8/copy-to-eax 2/imm32
-$draw-grapheme-on-screen-buffer:end:
+$draw-code-point-on-screen-buffer:end:
     # . restore registers
     5e/pop-to-esi
     # . epilogue
@@ -105,16 +110,16 @@ $draw-grapheme-on-screen-buffer:end:
     5d/pop-to-ebp
     c3/return
 
-wide-grapheme?:  # g: grapheme -> _/eax: boolean
+wide-code-point?:  # c: code-point -> _/eax: boolean
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
-    # eax = g
+    # eax = c
     8b/-> *(ebp+8) 0/r32/eax
-    # if (g >= 128) return  # characters beyond ASCII currently not supported
+    # if (c >= 128) return  # characters beyond ASCII currently not supported
     3d/compare-eax-and 0x80/imm32
-    0f 8d/jump-if->= $wide-grapheme?:end/disp32
-    # var letter-bitmap/eax = font[g]
+    0f 8d/jump-if->= $wide-code-point?:end/disp32
+    # var letter-bitmap/eax = font[c]
     69/multiply %eax 0x21/imm32/glyph-size 0/r32/eax
     05/add-to-eax 0x0010000c/imm32/Font  # see boot.subx
     # dispatch based on letter-bitmap->size
@@ -122,7 +127,7 @@ wide-grapheme?:  # g: grapheme -> _/eax: boolean
     25/and-eax-with  0xff/imm32
     3d/compare-eax-and 8/imm32
     0f 95/set-if-!= %eax
-$wide-grapheme?:end:
+$wide-code-point?:end:
     # . epilogue
     89/<- %esp 5/r32/ebp
     5d/pop-to-ebp
@@ -130,7 +135,7 @@ $wide-grapheme?:end:
 
 # buffer: naked address to raw screen RAM without a length
 # letter-bitmap: naked address to 8-pixel wide font glyph
-draw-narrow-grapheme-on-screen-buffer:  # buffer: (addr byte), letter-bitmap: (addr byte), x: int, y: int, color: int, background-color: int, screen-width: int, screen-height: int
+draw-narrow-code-point-on-screen-buffer:  # buffer: (addr byte), letter-bitmap: (addr byte), x: int, y: int, color: int, background-color: int, screen-width: int, screen-height: int
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -163,7 +168,7 @@ draw-narrow-grapheme-on-screen-buffer:  # buffer: (addr byte), letter-bitmap: (a
       #
       e9/jump loop/disp32
     }
-$draw-narrow-grapheme-on-screen-buffer:end:
+$draw-narrow-code-point-on-screen-buffer:end:
     # . restore registers
     5f/pop-to-edi
     5e/pop-to-esi
@@ -176,7 +181,7 @@ $draw-narrow-grapheme-on-screen-buffer:end:
 
 # buffer: naked address to raw screen RAM without a length
 # letter-bitmap: naked address to 16-pixel wide font glyph
-draw-wide-grapheme-on-screen-buffer:  # buffer: (addr byte), letter-bitmap: (addr byte), x: int, y: int, color: int, background-color: int, screen-width: int, screen-height: int
+draw-wide-code-point-on-screen-buffer:  # buffer: (addr byte), letter-bitmap: (addr byte), x: int, y: int, color: int, background-color: int, screen-width: int, screen-height: int
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -219,7 +224,7 @@ draw-wide-grapheme-on-screen-buffer:  # buffer: (addr byte), letter-bitmap: (add
       #
       e9/jump loop/disp32
     }
-$draw-wide-grapheme-on-screen-buffer:end:
+$draw-wide-code-point-on-screen-buffer:end:
     # . restore registers
     5f/pop-to-edi
     5e/pop-to-esi
@@ -260,11 +265,11 @@ draw-run-of-pixels-from-glyph:  # buffer: (addr byte), glyph-byte: byte, x: int,
       {
         73/jump-if-not-CF break/disp8
         (pixel-on-screen-buffer *(ebp+8) %eax *(ebp+0x14) *(ebp+0x18) *(ebp+0x20) *(ebp+0x24))
-        eb/jump $draw-grapheme-on-screen-buffer:continue/disp8
+        eb/jump $draw-code-point-on-screen-buffer:continue/disp8
       }
       # otherwise use the background color
       (pixel-on-screen-buffer *(ebp+8) %eax *(ebp+0x14) *(ebp+0x1c) *(ebp+0x20) *(ebp+0x24))
-$draw-grapheme-on-screen-buffer:continue:
+$draw-code-point-on-screen-buffer:continue:
       # --x
       48/decrement-eax
       #
@@ -318,7 +323,7 @@ $set-cursor-position-on-real-screen:end:
 #     tracking what was on the screen at this position before and passing it
 #     in again.
 #   - does not stop showing the cursor at this location when the cursor moves
-draw-cursor-on-real-screen:  # g: grapheme
+draw-cursor-on-real-screen:  # c: code-point
     # . prologue
     55/push-ebp
     89/<- %ebp 4/r32/esp
@@ -327,7 +332,7 @@ draw-cursor-on-real-screen:  # g: grapheme
     51/push-ecx
     #
     (cursor-position-on-real-screen)  # => eax, ecx
-    (draw-grapheme-on-real-screen *(ebp+8) %eax %ecx 0 7)  # => eax
+    (draw-code-point-on-real-screen *(ebp+8) %eax %ecx 0 7)  # => eax
 $draw-cursor-on-real-screen:end:
     # . restore registers
     59/pop-to-ecx
diff --git a/317abort.subx b/317abort.subx
index e4f07971..6b0fc7c7 100644
--- a/317abort.subx
+++ b/317abort.subx
@@ -311,7 +311,7 @@ draw-slice-wrapping-right-then-down-from-cursor-over-full-screen:  # screen: (ad
       73/jump-if-addr>= break/disp8
       # print *curr
       8a/byte-> *ecx 0/r32/eax
-      (draw-grapheme-at-cursor-over-full-screen *(ebp+8) %eax *(ebp+0x14) *(ebp+0x18))
+      (draw-code-point-at-cursor-over-full-screen *(ebp+8) %eax *(ebp+0x14) *(ebp+0x18))
       #
       41/increment-ecx
       #
diff --git a/400.mu b/400.mu
index fe2263a4..f0fe08f5 100644
--- a/400.mu
+++ b/400.mu
@@ -1,11 +1,11 @@
 # screen
 sig pixel-on-real-screen x: int, y: int, color: int
-sig draw-grapheme-on-real-screen g: grapheme, x: int, y: int, color: int, background-color: int -> _/eax: int
-sig draw-grapheme-on-screen-array screen-data: (addr array byte), g: grapheme, x: int, y: int, color: int, background-color: int, screen-width: int, screen-height: int -> _/eax: int
-sig wide-grapheme? g: grapheme -> _/eax: boolean
+sig draw-code-point-on-real-screen c: code-point, x: int, y: int, color: int, background-color: int -> _/eax: int
+sig draw-code-point-on-screen-array screen-data: (addr array byte), c: code-point, x: int, y: int, color: int, background-color: int, screen-width: int, screen-height: int -> _/eax: int
+sig wide-code-point? c: code-point -> _/eax: boolean
 sig cursor-position-on-real-screen -> _/eax: int, _/ecx: int
 sig set-cursor-position-on-real-screen x: int, y: int
-sig draw-cursor-on-real-screen g: grapheme
+sig draw-cursor-on-real-screen c: code-point
 sig color-rgb color: int -> _/ecx: int, _/edx: int, _/ebx: int
 
 # timer
diff --git a/403unicode.mu b/403unicode.mu
index 6ec30c3d..be002311 100644
--- a/403unicode.mu
+++ b/403unicode.mu
@@ -7,18 +7,21 @@
 # Graphemes may consist of multiple code points.
 #
 # Mu graphemes are always represented in utf-8, and they are required to fit
-# in 4 bytes.
+# in 4 bytes. (This can be confusing if you focus just on ASCII, where Mu's
+# graphemes and code-points are identical.)
 #
 # Mu doesn't currently support combining code points, or graphemes made of
 # multiple code points. One day we will.
-# We also don't currently support code points that translate into multiple
-# or wide graphemes. (In particular, Tab will never be supported.)
+#   https://en.wikipedia.org/wiki/Combining_character
+
+fn to-code-point in: grapheme -> _/eax: code-point {
+  var g/eax: grapheme <- copy in
+  var result/eax: code-point <- copy g  # TODO: support non-ASCII
+  return result
+}
 
 # transliterated from tb_utf8_unicode_to_char in https://github.com/nsf/termbox
 # https://wiki.tcl-lang.org/page/UTF%2D8+bit+by+bit explains the algorithm
-#
-# The day we want to support combining characters, this function will need to
-# take multiple code points. Or something.
 fn to-grapheme in: code-point -> _/eax: grapheme {
   var c/eax: int <- copy in
   var num-trailers/ecx: int <- copy 0
diff --git a/500fake-screen.mu b/500fake-screen.mu
index 1e4f7b72..be7d9e0f 100644
--- a/500fake-screen.mu
+++ b/500fake-screen.mu
@@ -23,7 +23,7 @@ type screen {
 }
 
 type screen-cell {
-  data: grapheme
+  data: code-point
   color: int
   background-color: int
   unused?: boolean
@@ -89,27 +89,27 @@ fn screen-size _screen: (addr screen) -> _/eax: int, _/ecx: int {
 
 # testable screen primitive
 # return number of 8x16 units drawn
-fn draw-grapheme _screen: (addr screen), g: grapheme, x: int, y: int, color: int, background-color: int -> _/eax: int {
+fn draw-code-point _screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int -> _/eax: int {
   var screen/esi: (addr screen) <- copy _screen
   {
     compare screen, 0
     break-if-!=
-    var result/eax: int <- draw-grapheme-on-real-screen g, x, y, color, background-color
+    var result/eax: int <- draw-code-point-on-real-screen c, x, y, color, background-color
     return result
   }
   # fake screen
-  var wide?/eax: boolean <- wide-grapheme? g
+  var wide?/eax: boolean <- wide-code-point? c
   compare wide?, 0/false
   {
     break-if-=
-    draw-wide-grapheme-on-fake-screen screen, g, x, y, color, background-color
+    draw-wide-code-point-on-fake-screen screen, c, x, y, color, background-color
     return 2
   }
-  draw-narrow-grapheme-on-fake-screen screen, g, x, y, color, background-color
+  draw-narrow-code-point-on-fake-screen screen, c, x, y, color, background-color
   return 1
 }
 
-fn draw-narrow-grapheme-on-fake-screen _screen: (addr screen), g: grapheme, x: int, y: int, color: int, background-color: int {
+fn draw-narrow-code-point-on-fake-screen _screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int {
   var screen/esi: (addr screen) <- copy _screen
   # ignore if out of bounds
   {
@@ -145,9 +145,9 @@ fn draw-narrow-grapheme-on-fake-screen _screen: (addr screen), g: grapheme, x: i
   var data/eax: (addr array screen-cell) <- lookup *data-ah
   var offset/ecx: (offset screen-cell) <- compute-offset data, index
   var dest-cell/ecx: (addr screen-cell) <- index data, offset
-  var dest-grapheme/eax: (addr grapheme) <- get dest-cell, data
-  var g2/edx: grapheme <- copy g
-  copy-to *dest-grapheme, g2
+  var dest-code-point/eax: (addr code-point) <- get dest-cell, data
+  var c2/edx: code-point <- copy c
+  copy-to *dest-code-point, c2
   var dest-color/eax: (addr int) <- get dest-cell, color
   var src-color/edx: int <- copy color
   copy-to *dest-color, src-color
@@ -158,7 +158,7 @@ fn draw-narrow-grapheme-on-fake-screen _screen: (addr screen), g: grapheme, x: i
   copy-to *dest, 0/false
 }
 
-fn draw-wide-grapheme-on-fake-screen _screen: (addr screen), g: grapheme, x: int, y: int, color: int, background-color: int {
+fn draw-wide-code-point-on-fake-screen _screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int {
   var screen/esi: (addr screen) <- copy _screen
   # ignore if out of bounds
   {
@@ -169,7 +169,7 @@ fn draw-wide-grapheme-on-fake-screen _screen: (addr screen), g: grapheme, x: int
   {
     var xmax-addr/eax: (addr int) <- get screen, width
     var xmax/eax: int <- copy *xmax-addr
-    xmax <- decrement  # wide graphemes need an extra unit
+    xmax <- decrement  # wide code-points need an extra unit
     compare x, xmax
     break-if-<
     return
@@ -193,9 +193,9 @@ fn draw-wide-grapheme-on-fake-screen _screen: (addr screen), g: grapheme, x: int
     var data/eax: (addr array screen-cell) <- lookup *data-ah
     var offset/ecx: (offset screen-cell) <- compute-offset data, index
     var dest-cell/ecx: (addr screen-cell) <- index data, offset
-    var dest-grapheme/eax: (addr grapheme) <- get dest-cell, data
-    var g2/edx: grapheme <- copy g
-    copy-to *dest-grapheme, g2
+    var dest-code-point/eax: (addr code-point) <- get dest-cell, data
+    var c2/edx: code-point <- copy c
+    copy-to *dest-code-point, c2
     var dest-color/eax: (addr int) <- get dest-cell, color
     var src-color/edx: int <- copy color
     copy-to *dest-color, src-color
@@ -217,14 +217,6 @@ fn draw-wide-grapheme-on-fake-screen _screen: (addr screen), g: grapheme, x: int
   }
 }
 
-# we can't really render non-ASCII yet, but when we do we'll be ready
-# return number of 8x16 units drawn
-fn draw-code-point screen: (addr screen), c: code-point, x: int, y: int, color: int, background-color: int -> _/eax: int {
-  var g/eax: grapheme <- copy c
-  var result/eax: int <- draw-grapheme screen, g, x, y, color, background-color
-  return result
-}
-
 # fake screens only
 fn screen-cell-index _screen: (addr screen), x: int, y: int -> _/ecx: int {
   var screen/esi: (addr screen) <- copy _screen
@@ -298,18 +290,18 @@ fn set-cursor-position _screen: (addr screen), x: int, y: int {
   copy-to *dest, src
 }
 
-fn draw-cursor screen: (addr screen), g: grapheme {
+fn draw-cursor screen: (addr screen), c: code-point {
   {
     compare screen, 0
     break-if-!=
-    draw-cursor-on-real-screen g
+    draw-cursor-on-real-screen c
     return
   }
   # fake screen
   var cursor-x/eax: int <- copy 0
   var cursor-y/ecx: int <- copy 0
   cursor-x, cursor-y <- cursor-position screen
-  var dummy/eax: int <- draw-grapheme screen, g, cursor-x, cursor-y, 0/fg, 7/bg
+  var dummy/eax: int <- draw-code-point screen, c, cursor-x, cursor-y, 0/fg, 7/bg
 }
 
 fn clear-screen _screen: (addr screen) {
@@ -367,11 +359,11 @@ fn fake-screen-empty? _screen: (addr screen) -> _/eax: boolean {
     {
       compare x, *width
       break-if->=
-      var g/eax: grapheme <- screen-grapheme-at screen, x, y
+      var c/eax: code-point <- screen-code-point-at screen, x, y
       {
-        compare g, 0
+        compare c, 0
         break-if-=
-        compare g, 0x20/space
+        compare c, 0x20/space
         break-if-=
         return 0/false
       }
@@ -506,21 +498,21 @@ fn screen-cell-unused-at-index? _screen: (addr screen), _index: int -> _/eax: bo
   return *src
 }
 
-fn screen-grapheme-at _screen: (addr screen), x: int, y: int -> _/eax: grapheme {
+fn screen-code-point-at _screen: (addr screen), x: int, y: int -> _/eax: code-point {
   var screen/esi: (addr screen) <- copy _screen
   var index/ecx: int <- screen-cell-index screen, x, y
-  var result/eax: grapheme <- screen-grapheme-at-index screen, index
+  var result/eax: code-point <- screen-code-point-at-index screen, index
   return result
 }
 
-fn screen-grapheme-at-index _screen: (addr screen), _index: int -> _/eax: grapheme {
+fn screen-code-point-at-index _screen: (addr screen), _index: int -> _/eax: code-point {
   var screen/esi: (addr screen) <- copy _screen
   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
   var data/eax: (addr array screen-cell) <- lookup *data-ah
   var index/ecx: int <- copy _index
   var offset/ecx: (offset screen-cell) <- compute-offset data, index
   var cell/eax: (addr screen-cell) <- index data, offset
-  var src/eax: (addr grapheme) <- get cell, data
+  var src/eax: (addr code-point) <- get cell, data
   return *src
 }
 
@@ -661,11 +653,11 @@ fn copy-pixels _screen: (addr screen), target-screen: (addr screen) {
   }
 }
 
-# It turns out double-buffering graphemes is useless because rendering fonts
+# It turns out double-buffering screen-cells is useless because rendering fonts
 # takes too long. (At least under Qemu.)
-# So we'll instead convert graphemes to pixels when double-buffering.
+# So we'll instead convert screen-cells to pixels when double-buffering.
 # 'screen' must be a fake screen.
-fn convert-graphemes-to-pixels _screen: (addr screen) {
+fn convert-screen-cells-to-pixels _screen: (addr screen) {
   var screen/esi: (addr screen) <- copy _screen
   var width-a/ebx: (addr int) <- get screen, width
   var height-a/edx: (addr int) <- get screen, height
@@ -678,25 +670,25 @@ fn convert-graphemes-to-pixels _screen: (addr screen) {
     compare y, *height-a
     break-if->=
     var x/edi: int <- copy 0
-    $convert-graphemes-to-pixels:loop-x: {
+    $convert-screen-cells-to-pixels:loop-x: {
       compare x, *width-a
       break-if->=
       {
-        var tmp/eax: grapheme <- screen-grapheme-at screen, x, y
-        # skip null graphemes that only get created when clearing screen
+        var tmp/eax: code-point <- screen-code-point-at screen, x, y
+        # skip null code-points that only get created when clearing screen
         # there may be other pixels drawn there, and we don't want to clobber them
-        # this is a situation where fake screens aren't faithful to real screens; we don't support overlap between graphemes and raw pixels
+        # this is a situation where fake screens aren't faithful to real screens; we don't support overlap between screen-cells and raw pixels
         compare tmp, 0
         break-if-=
-        var g: grapheme
-        copy-to g, tmp
+        var c: code-point
+        copy-to c, tmp
         var tmp/eax: int <- screen-color-at screen, x, y
         var fg: int
         copy-to fg, tmp
         var bg/eax: int <- screen-background-color-at screen, x, y
-        var offset/eax: int <- draw-grapheme-on-screen-array data, g, x, y, fg, bg, *width-a, *height-a
+        var offset/eax: int <- draw-code-point-on-screen-array data, c, x, y, fg, bg, *width-a, *height-a
         x <- add offset
-        loop $convert-graphemes-to-pixels:loop-x
+        loop $convert-screen-cells-to-pixels:loop-x
       }
       x <- increment
       loop
diff --git a/501draw-text.mu b/501draw-text.mu
index 4a415ddd..0b74c160 100644
--- a/501draw-text.mu
+++ b/501draw-text.mu
@@ -81,11 +81,11 @@ fn move-cursor-to-left-margin-of-next-line screen: (addr screen) {
   set-cursor-position screen, cursor-x, cursor-y
 }
 
-fn draw-grapheme-at-cursor-over-full-screen screen: (addr screen), g: grapheme, color: int, background-color: int {
+fn draw-code-point-at-cursor-over-full-screen screen: (addr screen), c: code-point, color: int, background-color: int {
   var cursor-x/eax: int <- copy 0
   var cursor-y/ecx: int <- copy 0
   cursor-x, cursor-y <- cursor-position screen
-  var _offset/eax: int <- draw-grapheme screen, g, cursor-x, cursor-y, color, background-color
+  var _offset/eax: int <- draw-code-point screen, c, cursor-x, cursor-y, color, background-color
   var offset/edx: int <- copy _offset
   var width/eax: int <- copy 0
   var dummy/ecx: int <- copy 0
@@ -100,11 +100,6 @@ fn draw-grapheme-at-cursor-over-full-screen screen: (addr screen), g: grapheme,
   }
 }
 
-fn draw-code-point-at-cursor-over-full-screen screen: (addr screen), c: code-point, color: int, background-color: int {
-  var g/eax: grapheme <- copy c
-  draw-grapheme-at-cursor-over-full-screen screen, g, color, background-color
-}
-
 # draw a single line of text from x, y to xmax
 # return the next 'x' coordinate
 # if there isn't enough space, truncate
@@ -125,7 +120,8 @@ fn draw-stream-rightward screen: (addr screen), stream: (addr stream byte), x: i
     var g/eax: grapheme <- read-grapheme stream
     compare g, 0xffffffff/end-of-file
     break-if-=
-    var offset/eax: int <- draw-grapheme screen, g, xcurr, y, color, background-color
+    var c/eax: code-point <- to-code-point g
+    var offset/eax: int <- draw-code-point screen, c, xcurr, y, color, background-color
     xcurr <- add offset
     loop
   }
@@ -156,8 +152,8 @@ fn draw-text-rightward-from-cursor-over-full-screen screen: (addr screen), text:
   draw-text-rightward-from-cursor screen, text, width, color, background-color
 }
 
-fn render-grapheme screen: (addr screen), g: grapheme, xmin: int, ymin: int, xmax: int, ymax: int, x: int, y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
-  compare g, 0xa/newline
+fn render-code-point screen: (addr screen), c: code-point, xmin: int, ymin: int, xmax: int, ymax: int, x: int, y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
+  compare c, 0xa/newline
   var x/ecx: int <- copy x
   {
     break-if-!=
@@ -167,7 +163,7 @@ fn render-grapheme screen: (addr screen), g: grapheme, xmin: int, ymin: int, xma
     increment y
     return x, y
   }
-  var offset/eax: int <- draw-grapheme screen, g, x, y, color, background-color
+  var offset/eax: int <- draw-code-point screen, c, x, y, color, background-color
   x <- add offset
   compare x, xmax
   {
@@ -210,15 +206,16 @@ fn draw-text-wrapping-right-then-down screen: (addr screen), _text: (addr array
 fn draw-stream-wrapping-right-then-down screen: (addr screen), stream: (addr stream byte), xmin: int, ymin: int, xmax: int, ymax: int, x: int, y: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
   var xcurr/eax: int <- copy x
   var ycurr/ecx: int <- copy y
-  var g/ebx: grapheme <- copy 0
+  var c/ebx: code-point <- copy 0
   {
     {
-      var _g/eax: grapheme <- read-grapheme stream
-      g <- copy _g
+      var g/eax: grapheme <- read-grapheme stream
+      var _c/eax: code-point <- to-code-point g
+      c <- copy _c
     }
-    compare g, 0xffffffff/end-of-file
+    compare c, 0xffffffff/end-of-file
     break-if-=
-    xcurr, ycurr <- render-grapheme screen, g, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
+    xcurr, ycurr <- render-code-point screen, c, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
     loop
   }
   set-cursor-position screen, xcurr, ycurr
@@ -301,7 +298,8 @@ fn draw-int32-hex-wrapping-right-then-down screen: (addr screen), n: int, xmin:
     var g/eax: grapheme <- read-grapheme stream
     compare g, 0xffffffff/end-of-file
     break-if-=
-    var offset/eax: int <- draw-grapheme screen, g, xcurr, ycurr, color, background-color
+    var c/eax: code-point <- to-code-point g
+    var offset/eax: int <- draw-code-point screen, c, xcurr, ycurr, color, background-color
     xcurr <- add offset
     compare xcurr, xmax
     {
@@ -355,7 +353,8 @@ fn draw-int32-decimal-wrapping-right-then-down screen: (addr screen), n: int, xm
     var g/eax: grapheme <- read-grapheme stream
     compare g, 0xffffffff/end-of-file
     break-if-=
-    var offset/eax: int <- draw-grapheme screen, g, xcurr, ycurr, color, background-color
+    var c/eax: code-point <- to-code-point g
+    var offset/eax: int <- draw-code-point screen, c, xcurr, ycurr, color, background-color
     xcurr <- add offset
     compare xcurr, xmax
     {
@@ -422,7 +421,8 @@ fn draw-stream-downward screen: (addr screen), stream: (addr stream byte), x: in
     var g/eax: grapheme <- read-grapheme stream
     compare g, 0xffffffff/end-of-file
     break-if-=
-    var dummy/eax: int <- draw-grapheme screen, g, x, ycurr, color, background-color
+    var c/eax: code-point <- to-code-point g
+    var dummy/eax: int <- draw-code-point screen, c, x, ycurr, color, background-color
     ycurr <- increment
     loop
   }
@@ -463,7 +463,8 @@ fn draw-stream-wrapping-down-then-right screen: (addr screen), stream: (addr str
     var g/eax: grapheme <- read-grapheme stream
     compare g, 0xffffffff/end-of-file
     break-if-=
-    var offset/eax: int <- draw-grapheme screen, g, xcurr, ycurr, color, background-color
+    var c/eax: code-point <- to-code-point g
+    var offset/eax: int <- draw-code-point screen, c, xcurr, ycurr, color, background-color
     ycurr <- increment
     compare ycurr, ymax
     {
diff --git a/504test-screen.mu b/504test-screen.mu
index aef85ac6..f43c6386 100644
--- a/504test-screen.mu
+++ b/504test-screen.mu
@@ -24,32 +24,33 @@ fn check-screen-row-from _screen: (addr screen), x: int, y: int, expected: (addr
       var unused?/eax: boolean <- screen-cell-unused-at-index? screen, index
       compare unused?, 0/false
       break-if-!=
-      var _g/eax: grapheme <- screen-grapheme-at-index screen, index
-      var g/ebx: grapheme <- copy _g
+      var _c/eax: code-point <- screen-code-point-at-index screen, index
+      var c/ebx: code-point <- copy _c
       var expected-grapheme/eax: grapheme <- read-grapheme e-addr
+      var expected-code-point/eax: code-point <- to-code-point expected-grapheme
       # compare graphemes
       $check-screen-row-from:compare-graphemes: {
-        # if expected-grapheme is space, null grapheme is also ok
+        # if expected-code-point is space, null grapheme is also ok
         {
-          compare expected-grapheme, 0x20
+          compare expected-code-point, 0x20
           break-if-!=
-          compare g, 0
+          compare c, 0
           break-if-= $check-screen-row-from:compare-graphemes
         }
-        # if (g == expected-grapheme) print "."
-        compare g, expected-grapheme
+        # if (c == expected-code-point) print "."
+        compare c, expected-code-point
         break-if-=
         # otherwise print an error
         failure-count <- increment
         draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
         draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ": expected '", 3/fg/cyan, 0/bg
-        draw-grapheme-at-cursor-over-full-screen 0/screen, expected-grapheme, 3/cyan, 0/bg
+        draw-code-point-at-cursor-over-full-screen 0/screen, expected-code-point, 3/cyan, 0/bg
         draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "' at (", 3/fg/cyan, 0/bg
         draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, x, 3/fg/cyan, 0/bg
         draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ", ", 3/fg/cyan, 0/bg
         draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, y, 3/fg/cyan, 0/bg
         draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ") but observed '", 3/fg/cyan, 0/bg
-        draw-grapheme-at-cursor-over-full-screen 0/screen, g, 3/cyan, 0/bg
+        draw-code-point-at-cursor-over-full-screen 0/screen, c, 3/cyan, 0/bg
         draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "'", 3/fg/cyan, 0/bg
         move-cursor-to-left-margin-of-next-line 0/screen
       }
@@ -90,21 +91,22 @@ fn check-screen-row-in-color-from _screen: (addr screen), fg: int, y: int, x: in
       var unused?/eax: boolean <- screen-cell-unused-at-index? screen, index
       compare unused?, 0/false
       break-if-!=
-      var _g/eax: grapheme <- screen-grapheme-at-index screen, index
-      var g/ebx: grapheme <- copy _g
-      var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
-      var expected-grapheme/edi: grapheme <- copy _expected-grapheme
+      var _c/eax: code-point <- screen-code-point-at-index screen, index
+      var c/ebx: code-point <- copy _c
+      var expected-grapheme/eax: grapheme <- read-grapheme e-addr
+      var _expected-code-point/eax: code-point <- to-code-point expected-grapheme
+      var expected-code-point/edi: code-point <- copy _expected-code-point
       $check-screen-row-in-color-from:compare-cells: {
-        # if expected-grapheme is space, null grapheme is also ok
+        # if expected-code-point is space, null grapheme is also ok
         {
-          compare expected-grapheme, 0x20
+          compare expected-code-point, 0x20
           break-if-!=
-          compare g, 0
+          compare c, 0
           break-if-= $check-screen-row-in-color-from:compare-cells
         }
-        # if expected-grapheme is space, a different color is ok
+        # if expected-code-point is space, a different color is ok
         {
-          compare expected-grapheme, 0x20
+          compare expected-code-point, 0x20
           break-if-!=
           var color/eax: int <- screen-color-at-index screen, index
           compare color, fg
@@ -112,8 +114,8 @@ fn check-screen-row-in-color-from _screen: (addr screen), fg: int, y: int, x: in
         }
         # compare graphemes
         $check-screen-row-in-color-from:compare-graphemes: {
-          # if (g == expected-grapheme) print "."
-          compare g, expected-grapheme
+          # if (c == expected-code-point) print "."
+          compare c, expected-code-point
           {
             break-if-!=
             draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ".", 3/fg/cyan, 0/bg
@@ -123,13 +125,13 @@ fn check-screen-row-in-color-from _screen: (addr screen), fg: int, y: int, x: in
           count-test-failure
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ": expected '", 3/fg/cyan, 0/bg
-          draw-grapheme-at-cursor-over-full-screen 0/screen, expected-grapheme, 3/cyan, 0/bg
+          draw-code-point-at-cursor-over-full-screen 0/screen, expected-code-point, 3/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "' at (", 3/fg/cyan, 0/bg
           draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, x, 3/fg/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ", ", 3/fg/cyan, 0/bg
           draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, y, 3/fg/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ") but observed '", 3/fg/cyan, 0/bg
-          draw-grapheme-at-cursor-over-full-screen 0/screen, g, 3/cyan, 0/bg
+          draw-code-point-at-cursor-over-full-screen 0/screen, c, 3/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "'", 3/fg/cyan, 0/bg
           move-cursor-to-left-margin-of-next-line 0/screen
         }
@@ -145,7 +147,7 @@ fn check-screen-row-in-color-from _screen: (addr screen), fg: int, y: int, x: in
           count-test-failure
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ": expected '", 3/fg/cyan, 0/bg
-          draw-grapheme-at-cursor-over-full-screen 0/screen, expected-grapheme, 3/cyan, 0/bg
+          draw-code-point-at-cursor-over-full-screen 0/screen, expected-code-point, 3/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "' at (", 3/fg/cyan, 0/bg
           draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, x, 3/fg/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ", ", 3/fg/cyan, 0/bg
@@ -183,21 +185,22 @@ fn check-screen-row-in-background-color-from _screen: (addr screen), bg: int, y:
       var unused?/eax: boolean <- screen-cell-unused-at-index? screen, index
       compare unused?, 0/false
       break-if-!=
-      var _g/eax: grapheme <- screen-grapheme-at-index screen, index
-      var g/ebx: grapheme <- copy _g
-      var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
-      var expected-grapheme/edi: grapheme <- copy _expected-grapheme
+      var _g/eax: code-point <- screen-code-point-at-index screen, index
+      var g/ebx: code-point <- copy _g
+      var expected-grapheme/eax: grapheme <- read-grapheme e-addr
+      var _expected-code-point/eax: code-point <- to-code-point expected-grapheme
+      var expected-code-point/edi: code-point <- copy _expected-code-point
       $check-screen-row-in-background-color-from:compare-cells: {
-        # if expected-grapheme is space, null grapheme is also ok
+        # if expected-code-point is space, null grapheme is also ok
         {
-          compare expected-grapheme, 0x20
+          compare expected-code-point, 0x20
           break-if-!=
           compare g, 0
           break-if-= $check-screen-row-in-background-color-from:compare-cells
         }
-        # if expected-grapheme is space, a different background-color is ok
+        # if expected-code-point is space, a different background-color is ok
         {
-          compare expected-grapheme, 0x20
+          compare expected-code-point, 0x20
           break-if-!=
           var background-color/eax: int <- screen-background-color-at-index screen, index
           compare background-color, bg
@@ -205,8 +208,8 @@ fn check-screen-row-in-background-color-from _screen: (addr screen), bg: int, y:
         }
         # compare graphemes
         $check-screen-row-in-background-color-from:compare-graphemes: {
-          # if (g == expected-grapheme) print "."
-          compare g, expected-grapheme
+          # if (g == expected-code-point) print "."
+          compare g, expected-code-point
           {
             break-if-!=
             draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ".", 3/fg/cyan, 0/bg
@@ -216,13 +219,13 @@ fn check-screen-row-in-background-color-from _screen: (addr screen), bg: int, y:
           count-test-failure
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ": expected '", 3/fg/cyan, 0/bg
-          draw-grapheme-at-cursor-over-full-screen 0/screen, expected-grapheme, 3/cyan, 0/bg
+          draw-code-point-at-cursor-over-full-screen 0/screen, expected-code-point, 3/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "' at (", 3/fg/cyan, 0/bg
           draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, x, 3/fg/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ", ", 3/fg/cyan, 0/bg
           draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, y, 3/fg/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ") but observed '", 3/fg/cyan, 0/bg
-          draw-grapheme-at-cursor-over-full-screen 0/screen, g, 3/cyan, 0/bg
+          draw-code-point-at-cursor-over-full-screen 0/screen, g, 3/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "'", 3/fg/cyan, 0/bg
           move-cursor-to-left-margin-of-next-line 0/screen
           break $check-screen-row-in-background-color-from:compare-graphemes
@@ -239,7 +242,7 @@ fn check-screen-row-in-background-color-from _screen: (addr screen), bg: int, y:
           count-test-failure
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ": expected '", 3/fg/cyan, 0/bg
-          draw-grapheme-at-cursor-over-full-screen 0/screen, expected-grapheme, 3/cyan, 0/bg
+          draw-code-point-at-cursor-over-full-screen 0/screen, expected-code-point, 3/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, "' at (", 3/fg/cyan, 0/bg
           draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, x, 3/fg/cyan, 0/bg
           draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ", ", 3/fg/cyan, 0/bg
diff --git a/513grapheme-stack.mu b/513grapheme-stack.mu
index 2db873a0..40deb052 100644
--- a/513grapheme-stack.mu
+++ b/513grapheme-stack.mu
@@ -98,7 +98,12 @@ fn render-stack-from-bottom-wrapping-right-then-down screen: (addr screen), _sel
     compare i, *top-addr
     break-if->=
     {
-      var g/esi: (addr grapheme) <- index data, i
+      var c: code-point
+      {
+        var g/eax: (addr grapheme) <- index data, i
+        var tmp/eax: code-point <- to-code-point *g
+        copy-to c, tmp
+      }
       var fg: int
       {
         var tmp/eax: int <- copy color
@@ -109,7 +114,7 @@ fn render-stack-from-bottom-wrapping-right-then-down screen: (addr screen), _sel
         break-if-!=
         copy-to fg, 0xf/highlight
       }
-      x, y <- render-grapheme screen, *g, xmin, ymin, xmax, ymax, x, y, fg, background-color
+      x, y <- render-code-point screen, c, xmin, ymin, xmax, ymax, x, y, fg, background-color
     }
     i <- increment
     loop
@@ -152,8 +157,13 @@ fn render-stack-from-top-wrapping-right-then-down screen: (addr screen), _self:
     break-if-=
     compare i, 0
     break-if-<
-    var g/esi: (addr grapheme) <- index data, i
-    x, y <- render-grapheme screen, *g, xmin, ymin, xmax, ymax, x, y, background-color, color
+    var c: code-point
+    {
+      var g/eax: (addr grapheme) <- index data, i
+      var tmp/eax: code-point <- to-code-point *g
+      copy-to c, tmp
+    }
+    x, y <- render-code-point screen, c, xmin, ymin, xmax, ymax, x, y, background-color, color
     i <- decrement
   }
   # remaining iterations
@@ -172,8 +182,13 @@ fn render-stack-from-top-wrapping-right-then-down screen: (addr screen), _self:
       copy-to fg, 0xf/highlight
     }
     #
-    var g/esi: (addr grapheme) <- index data, i
-    x, y <- render-grapheme screen, *g, xmin, ymin, xmax, ymax, x, y, fg, background-color
+    var c: code-point
+    {
+      var g/eax: (addr grapheme) <- index data, i
+      var tmp/eax: code-point <- to-code-point *g
+      copy-to c, tmp
+    }
+    x, y <- render-code-point screen, c, xmin, ymin, xmax, ymax, x, y, fg, background-color
     i <- decrement
     loop
   }
diff --git a/514gap-buffer.mu b/514gap-buffer.mu
index 39745d1f..4acab54d 100644
--- a/514gap-buffer.mu
+++ b/514gap-buffer.mu
@@ -409,8 +409,8 @@ fn render-gap-buffer-wrapping-right-then-down screen: (addr screen), _gap: (addr
     bg <- copy color
   }
   # print a grapheme either way so that cursor position doesn't affect printed width
-  var space/edx: grapheme <- copy 0x20
-  x2, y2 <- render-grapheme screen, space, xmin, ymin, xmax, ymax, x2, y2, fg, bg
+  var space/edx: code-point <- copy 0x20
+  x2, y2 <- render-code-point screen, space, xmin, ymin, xmax, ymax, x2, y2, fg, bg
   return x2, y2
 }
 
diff --git a/apps/colors.mu b/apps/colors.mu
index c58fec35..3d9f1bc1 100644
--- a/apps/colors.mu
+++ b/apps/colors.mu
@@ -27,8 +27,8 @@ fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk)
       loop-if-=
       var key2/eax: int <- copy key
       append-byte in, key2
-      var g/eax: grapheme <- copy key2
-      draw-grapheme-at-cursor-over-full-screen screen, g, 0xf/fg, 0/bg
+      var c/eax: code-point <- copy key2  # TODO: unicode input
+      draw-code-point-at-cursor-over-full-screen screen, c, 0xf/fg, 0/bg
       loop
     }
     clear-screen screen
diff --git a/apps/rpn.mu b/apps/rpn.mu
index ac54457d..4fd8ccb6 100644
--- a/apps/rpn.mu
+++ b/apps/rpn.mu
@@ -19,7 +19,7 @@ fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk)
   var in-storage: (stream byte 0x80)
   var in/esi: (addr stream byte) <- address in-storage
   var y/ecx: int <- copy 0
-  var space/edx: grapheme <- copy 0x20
+  var space/edx: code-point <- copy 0x20
   # read-eval-print loop
   {
     # print prompt
@@ -35,12 +35,12 @@ fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk)
       loop-if-=
       var key2/eax: int <- copy key
       append-byte in, key2
-      var g/eax: grapheme <- copy key2
-      draw-grapheme-at-cursor-over-full-screen screen, g, 0xf/fg, 0/bg
+      var c/eax: code-point <- copy key2
+      draw-code-point-at-cursor-over-full-screen screen, c, 0xf/fg, 0/bg
       loop
     }
     # clear cursor
-    draw-grapheme-at-cursor-over-full-screen screen, space, 3/fg/never-used, 0/bg
+    draw-code-point-at-cursor-over-full-screen screen, space, 3/fg/never-used, 0/bg
     # parse and eval
     var out/eax: int <- simplify in
     # print
diff --git a/browse-slack/environment.mu b/browse-slack/environment.mu
index 0b829d1d..6277d53a 100644
--- a/browse-slack/environment.mu
+++ b/browse-slack/environment.mu
@@ -378,7 +378,7 @@ fn render-search-input screen: (addr screen), _env: (addr environment) {
     compare x, 0x4a/end-search
     break-if->
     var y/ecx: int <- copy 0
-    x, y <- render-grapheme screen, 0x5f/underscore, 0/xmin 1/ymin, 0x80/xmax, 1/ymax, x, 1/y, 0xf/fg 0/bg
+    x, y <- render-code-point screen, 0x5f/underscore, 0/xmin 1/ymin, 0x80/xmax, 1/ymax, x, 1/y, 0xf/fg 0/bg
     loop
   }
 }
@@ -580,17 +580,17 @@ fn draw-json-stream-wrapping-right-then-down screen: (addr screen), stream: (add
   var xcurr/eax: int <- copy x
   var ycurr/ecx: int <- copy y
   {
-    var g/ebx: grapheme <- read-json-grapheme stream
-    compare g, 0xffffffff/end-of-file
+    var c/ebx: code-point <- read-json-code-point stream
+    compare c, 0xffffffff/end-of-file
     break-if-=
     $draw-json-stream-wrapping-right-then-down:render-grapheme: {
-      compare g, 0x5c/backslash
+      compare c, 0x5c/backslash
       {
         break-if-!=
-        xcurr, ycurr <- render-json-escaped-grapheme screen, stream, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
+        xcurr, ycurr <- render-json-escaped-code-point screen, stream, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
         break $draw-json-stream-wrapping-right-then-down:render-grapheme
       }
-      xcurr, ycurr <- render-grapheme screen, g, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
+      xcurr, ycurr <- render-code-point screen, c, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
     }
     loop
   }
@@ -599,15 +599,16 @@ fn draw-json-stream-wrapping-right-then-down screen: (addr screen), stream: (add
 }
 
 # just return a different register
-fn read-json-grapheme stream: (addr stream byte) -> _/ebx: grapheme {
-  var result/eax: grapheme <- read-grapheme stream
+fn read-json-code-point stream: (addr stream byte) -> _/ebx: code-point {
+  var g/eax: grapheme <- read-grapheme stream
+  var result/eax: code-point <- to-code-point g
   return result
 }
 
 # '\' encountered
 # https://www.json.org/json-en.html
-fn render-json-escaped-grapheme screen: (addr screen), stream: (addr stream byte), xmin: int, ymin: int, xmax: int, ymax: int, xcurr: int, ycurr: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
-  var g/ebx: grapheme <- read-json-grapheme stream
+fn render-json-escaped-code-point screen: (addr screen), stream: (addr stream byte), xmin: int, ymin: int, xmax: int, ymax: int, xcurr: int, ycurr: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
+  var g/ebx: code-point <- read-json-code-point stream
   compare g, 0xffffffff/end-of-file
   {
     break-if-!=
@@ -647,16 +648,16 @@ fn render-json-escaped-grapheme screen: (addr screen), stream: (addr stream byte
   {
     compare g, 0x75/u
     break-if-!=
-    x, y <- render-json-escaped-unicode-grapheme screen, stream, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
+    x, y <- render-json-escaped-unicode-code-point screen, stream, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
     return x, y
   }
   # most characters escape to themselves
-  x, y <- render-grapheme screen, g, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
+  x, y <- render-code-point screen, g, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
   return x, y
 }
 
 # '\u' encountered
-fn render-json-escaped-unicode-grapheme screen: (addr screen), stream: (addr stream byte), xmin: int, ymin: int, xmax: int, ymax: int, xcurr: int, ycurr: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
+fn render-json-escaped-unicode-code-point screen: (addr screen), stream: (addr stream byte), xmin: int, ymin: int, xmax: int, ymax: int, xcurr: int, ycurr: int, color: int, background-color: int -> _/eax: int, _/ecx: int {
   var ustream-storage: (stream byte 4)
   var ustream/esi: (addr stream byte) <- address ustream-storage
   # slurp 4 bytes exactly
@@ -679,7 +680,7 @@ fn render-json-escaped-unicode-grapheme screen: (addr screen), stream: (addr str
     break-if-=
     var x/eax: int <- copy 0
     var y/ecx: int <- copy 0
-    x, y <- render-grapheme screen, 0x2d/dash, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
+    x, y <- render-code-point screen, 0x2d/dash, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
     return x, y
   }
   # \u2014 = -
@@ -689,7 +690,7 @@ fn render-json-escaped-unicode-grapheme screen: (addr screen), stream: (addr str
     break-if-=
     var x/eax: int <- copy 0
     var y/ecx: int <- copy 0
-    x, y <- render-grapheme screen, 0x2d/dash, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
+    x, y <- render-code-point screen, 0x2d/dash, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
     return x, y
   }
   # \u2018 = '
@@ -699,7 +700,7 @@ fn render-json-escaped-unicode-grapheme screen: (addr screen), stream: (addr str
     break-if-=
     var x/eax: int <- copy 0
     var y/ecx: int <- copy 0
-    x, y <- render-grapheme screen, 0x27/quote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
+    x, y <- render-code-point screen, 0x27/quote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
     return x, y
   }
   # \u2019 = '
@@ -709,7 +710,7 @@ fn render-json-escaped-unicode-grapheme screen: (addr screen), stream: (addr str
     break-if-=
     var x/eax: int <- copy 0
     var y/ecx: int <- copy 0
-    x, y <- render-grapheme screen, 0x27/quote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
+    x, y <- render-code-point screen, 0x27/quote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
     return x, y
   }
   # \u201c = "
@@ -719,7 +720,7 @@ fn render-json-escaped-unicode-grapheme screen: (addr screen), stream: (addr str
     break-if-=
     var x/eax: int <- copy 0
     var y/ecx: int <- copy 0
-    x, y <- render-grapheme screen, 0x22/dquote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
+    x, y <- render-code-point screen, 0x22/dquote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
     return x, y
   }
   # \u201d = "
@@ -729,7 +730,7 @@ fn render-json-escaped-unicode-grapheme screen: (addr screen), stream: (addr str
     break-if-=
     var x/eax: int <- copy 0
     var y/ecx: int <- copy 0
-    x, y <- render-grapheme screen, 0x22/dquote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
+    x, y <- render-code-point screen, 0x22/dquote, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
     return x, y
   }
   # \u2022 = *
@@ -739,7 +740,7 @@ fn render-json-escaped-unicode-grapheme screen: (addr screen), stream: (addr str
     break-if-=
     var x/eax: int <- copy 0
     var y/ecx: int <- copy 0
-    x, y <- render-grapheme screen, 0x2a/asterisk, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
+    x, y <- render-code-point screen, 0x2a/asterisk, xmin, ymin, xmax, ymax, xcurr, ycurr, color, background-color
     return x, y
   }
   # \u2026 = ...
diff --git a/linux/403unicode.mu b/linux/403unicode.mu
index 9f857c7f..2baefd9f 100644
--- a/linux/403unicode.mu
+++ b/linux/403unicode.mu
@@ -11,14 +11,11 @@
 #
 # Mu doesn't currently support combining code points, or graphemes made of
 # multiple code points. One day we will.
-# We also don't currently support code points that translate into multiple
-# or wide graphemes. (In particular, Tab will never be supported.)
+# On Linux, we also don't currently support code points that translate into
+# multiple or wide graphemes. (In particular, Tab will never be supported.)
 
 # transliterated from tb_utf8_unicode_to_char in https://github.com/nsf/termbox
 # https://wiki.tcl-lang.org/page/UTF%2D8+bit+by+bit explains the algorithm
-#
-# The day we want to support combining characters, this function will need to
-# take multiple code points. Or something.
 fn to-grapheme in: code-point -> _/eax: grapheme {
   var c/eax: int <- copy in
   var num-trailers/ecx: int <- copy 0
diff --git a/shell/primitives.mu b/shell/primitives.mu
index d38eedbc..e955b531 100644
--- a/shell/primitives.mu
+++ b/shell/primitives.mu
@@ -3493,7 +3493,7 @@ fn apply-blit _args-ah: (addr handle cell), out: (addr handle cell), trace: (add
   var dest-ah/eax: (addr handle screen) <- get second, screen-data
   var dest/eax: (addr screen) <- lookup *dest-ah
   #
-  convert-graphemes-to-pixels src
+  convert-screen-cells-to-pixels src
   copy-pixels src, dest
 }
 
diff --git a/shell/sandbox.mu b/shell/sandbox.mu
index eb752b14..d50f47f0 100644
--- a/shell/sandbox.mu
+++ b/shell/sandbox.mu
@@ -255,8 +255,8 @@ fn render-empty-screen screen: (addr screen), _target-screen: (addr screen), xmi
 
 fn render-screen screen: (addr screen), _target-screen: (addr screen), xmin: int, ymin: int {
   var target-screen/esi: (addr screen) <- copy _target-screen
-  convert-graphemes-to-pixels target-screen  # might overwrite existing pixel data with graphemes
-                                             # overlapping the two is not supported
+  convert-screen-cells-to-pixels target-screen  # might overwrite existing pixel data with screen cells
+                                                # overlapping the two is not supported
   # pixel data
   {
     # screen top left pixels x y width height
@@ -383,10 +383,10 @@ fn print-screen-cell-of-fake-screen screen: (addr screen), _target: (addr screen
   var index/ecx: int <- screen-cell-index target, x, y
   var offset/ecx: (offset screen-cell) <- compute-offset data, index
   var src-cell/esi: (addr screen-cell) <- index data, offset
-  var src-grapheme/eax: (addr grapheme) <- get src-cell, data
+  var src-code-point/eax: (addr code-point) <- get src-cell, data
   var src-color/ecx: (addr int) <- get src-cell, color
   var src-background-color/edx: (addr int) <- get src-cell, background-color
-  draw-grapheme-at-cursor-over-full-screen screen, *src-grapheme, *src-color, *src-background-color
+  draw-code-point-at-cursor-over-full-screen screen, *src-code-point, *src-color, *src-background-color
 }
 
 fn render-sandbox-edit-menu screen: (addr screen), _self: (addr sandbox) {