Browse Source

Remove old code and update documentation

Justine Tunney 3 năm trước cách đây
mục cha
commit
4233210a86
14 tập tin đã thay đổi với 3757 bổ sung704 xóa
  1. 1 0
      LICENSE
  2. 9 51
      Makefile
  3. 57 23
      README.md
  4. 3481 0
      bestline.c
  5. 33 0
      bestline.h
  6. BIN
      bin/footprint.png
  7. BIN
      bin/lisp.elf.linux
  8. 173 166
      lisp.c
  9. 0 180
      lisp.h
  10. 0 36
      lisp.lds
  11. 3 2
      lisp.lisp
  12. 0 177
      realify.sed
  13. 0 23
      realify.sh
  14. 0 46
      start.S

+ 1 - 0
LICENSE

@@ -1,4 +1,5 @@
 Copyright 2020 Justine Alexandra Roberts Tunney
+Copyright 2021 Alain Greppin
 
 Permission to use, copy, modify, and/or distribute this software for
 any purpose with or without fee is hereby granted, provided that the

+ 9 - 51
Makefile

@@ -1,70 +1,28 @@
-CFLAGS	?= -g
-CFLAGS	+= -fno-pie
-LDFLAGS	+= -no-pie  # -s -static -N
-
-REALFLAGS =				\
-	-Os				\
-	-D__REAL_MODE__			\
-	-wrapper ./realify.sh		\
-	-ffixed-r8			\
-	-ffixed-r9			\
-	-ffixed-r10			\
-	-ffixed-r11			\
-	-ffixed-r12			\
-	-ffixed-r13			\
-	-ffixed-r14			\
-	-ffixed-r15			\
-	-mno-red-zone			\
-	-fcall-used-rbx			\
-	-fno-jump-tables		\
-	-fno-shrink-wrap		\
-	-fno-schedule-insns2		\
-	-flive-range-shrinkage		\
-	-fno-omit-frame-pointer		\
-	-momit-leaf-frame-pointer	\
-	-mpreferred-stack-boundary=3	\
-	-fno-delete-null-pointer-checks
-
 CLEANFILES =				\
 	lisp				\
 	lisp.o				\
-	lisp.real.o			\
+	bestline.o			\
 	sectorlisp.o			\
-	start.o				\
-	lisp.bin			\
 	sectorlisp.bin			\
-	lisp.bin.dbg			\
 	sectorlisp.bin.dbg
 
 .PHONY:	all
 all:	lisp				\
-	lisp.bin			\
-	lisp.bin.dbg			\
 	sectorlisp.bin			\
 	sectorlisp.bin.dbg
 
 .PHONY:	clean
-clean:;	$(RM) $(CLEANFILES)
+clean:;	$(RM) lisp lisp.o bestline.o sectorlisp.o sectorlisp.bin sectorlisp.bin.dbg
 
-lisp.bin.dbg: start.o lisp.real.o lisp.lds
-lisp:	lisp.o
-
-start.o: start.S Makefile
-lisp.o: lisp.c lisp.h Makefile
-lisp.real.o: lisp.c lisp.h Makefile
+lisp: lisp.o bestline.o
+lisp.o: lisp.c bestline.h
+bestline.o: bestline.c bestline.h
 
 sectorlisp.o: sectorlisp.S
-	$(AS)  -g -mtune=i386 -o $@ $<
+	$(AS) -g -mtune=i386 -o $@ $<
+
 sectorlisp.bin.dbg: sectorlisp.o
 	$(LD) -oformat:binary -Ttext=0x7600 -o $@ $<
-sectorlisp.bin: sectorlisp.bin.dbg
-	objcopy -SO binary sectorlisp.bin.dbg sectorlisp.bin
-
-%.real.o: %.c
-	$(CC) $(CPPFLAGS) $(CFLAGS) $(REALFLAGS) -c -o $@ $<
 
-%.bin.dbg:
-	$(LD) $(LDFLAGS) -static -o $@ $(patsubst %.lds,-T %.lds,$^)
-
-%.bin: %.bin.dbg
-	objcopy -SO binary $< $@
+sectorlisp.bin: sectorlisp.bin.dbg
+	objcopy -S -O binary sectorlisp.bin.dbg sectorlisp.bin

+ 57 - 23
README.md

@@ -1,37 +1,71 @@
 # sectorlisp
 
-sectorlisp is an effort to bootstrap John McCarthy's meta-circular
-evaluator on bare metal from a 512-byte boot sector.
+sectorlisp is a 512-byte implementation of LISP that's able to bootstrap
+John McCarthy's meta-circular evaluator on bare metal.
 
 ![Yo dawg, I heard you like LISP so I put a LISP in your LISP so you can eval while you eval](bin/yodawg.png)
 
-## Motivations
+## Overview
 
-Much of the information about LISP online tends to focus on
-[wild macros](http://www.paulgraham.com/onlisp.html),
-[JIT compilation](http://pixielang.org/), or its merits as
-[a better XML](http://www.defmacro.org/ramblings/lisp.html)
-as well as [a better JSON](https://stopa.io/post/265). However
-there's been comparatively little focus on the
-[primary materials](https://people.cs.umass.edu/~emery/classes/cmpsci691st/readings/PL/LISP.pdf)
-from the 1950's which emphasize the radically simple nature of
-LISP, as best evidenced by the meta-circular evaluator above.
+LISP has been described as the [Maxwell's equations of
+software](https://michaelnielsen.org/ddi/lisp-as-the-maxwells-equations-of-software/).
+Yet there's been very little focus to date on reducing these equations
+to their simplest possible form. Even the [original LISP
+paper](https://people.cs.umass.edu/~emery/classes/cmpsci691st/readings/PL/LISP.pdf)
+from the 1960's defines LISP with nonessential elements, e.g. `LABEL`.
+
+This project aims to solve that by doing three things:
+
+1. We provide a LISP implementation that's written in LISP, as a single
+   pure expression, using only the essential functions of the language.
+   See [lisp.lisp](lisp.lisp). It's the same meta-circular evaluator in
+   John McCarthy's paper from the 1960's, except with its bugs fixed,
+   dependencies included, and syntactic sugar removed.
+
+2. We provide a readable portable C reference implementation to show how
+   the meta-circular evaluator can be natively bootstrapped on POSIX
+   conforming platforms, with a pleasant readline-like interface. See
+   [lisp.c](lisp.c).
+
+2. We provide a 512-byte i8086 implementation of LISP that boots from
+   BIOS on personal computers. See [sectorlisp.S](sectorlisp.S). To the
+   best of our knowledge, this is the tiniest true LISP implementation
+   to date.
 
 <p align="center">
-  <img alt="Binary Footprint Comparison"
-       width="750" height="348" src="bin/footprint.png">
+  <img alt="Binary Footprint Comparison" src="bin/footprint.png">
 </p>
 
-This project aims to promote the radical simplicity of the essential
-elements of LISP's original design, by building the tiniest LISP machine
-possible. With a binary footprint less than one kilobyte, that's capable
-of running natively without dependencies on modern PCs, sectorlisp might
-be the tiniest self-hosting LISP interpreter to date. 
+## Getting Started
+
+See [lisp.lisp](lisp.lisp) for code examples that you can copy and paste
+into your LISP REPL.
+
+You can run the C implementation as follows:
+
+```sh
+$ make
+$ ./lisp
+```
+
+After running `make` you should see a `sectorlisp.bin` file, which is a
+master boot record you can put on a flopy disk and boot from BIOS. If
+you would prefer to run it in an emulator, we recommend using
+[Das Blinkenlights](https://justine.lol/blinkenlights/).
+
+```sh
+curl --compressed https://justine.lol/blinkenlights/blinkenlights-latest.com >blinkenlights.com
+chmod +x blinkenlights.com
+./blinkenlights.com -rt sectorlisp.bin
+```
+
+Alternatively you may use QEMU as follows:
+
+```sh
+qemu-system-i386 -nographic -fda sectorlisp.bin
+```
 
-We're still far off however from reaching our goal, which is to have
-sectorlisp be small enough to fit in the master boot record of a floppy
-disk, like [sectorforth](https://github.com/cesarblum/sectorforth). If
-you can help this project reach its goal, please send us a pull request!
+Further information may be found on [our wiki](https://github.com/jart/sectorlisp/wiki).
 
 ## Demo
 

+ 3481 - 0
bestline.c

@@ -0,0 +1,3481 @@
+/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8 -*-│
+│vi: set net ft=c ts=4 sts=4 sw=4 fenc=utf-8                                :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│                                                                              │
+│ Bestline ── Library for interactive pseudoteletypewriter command             │
+│             sessions using ANSI Standard X3.64 control sequences             │
+│                                                                              │
+│ OVERVIEW                                                                     │
+│                                                                              │
+│   Bestline is a fork of linenoise (a popular readline alternative)           │
+│   that fixes its bugs and adds the missing features while reducing           │
+│   binary footprint (surprisingly) by removing bloated dependencies           │
+│   which means you can finally have a permissively-licensed command           │
+│   prompt w/ a 30kb footprint that's nearly as good as gnu readline           │
+│                                                                              │
+│ EXAMPLE                                                                      │
+│                                                                              │
+│   main() {                                                                   │
+│       char *line;                                                            │
+│       while ((line = bestlineWithHistory("IN> ", "foo"))) {                  │
+│           fputs("OUT> ", stdout);                                            │
+│           fputs(line, stdout);                                               │
+│           fputs("\n", stdout);                                               │
+│           free(line);                                                        │
+│       }                                                                      │
+│   }                                                                          │
+│                                                                              │
+│ CHANGES                                                                      │
+│                                                                              │
+│   - Remove bell                                                              │
+│   - Add kill ring                                                            │
+│   - Fix flickering                                                           │
+│   - Add UTF-8 editing                                                        │
+│   - Add CTRL-R search                                                        │
+│   - Support unlimited lines                                                  │
+│   - Add parentheses awareness                                                │
+│   - React to terminal resizing                                               │
+│   - Don't generate .data section                                             │
+│   - Support terminal flow control                                            │
+│   - Make history loading 10x faster                                          │
+│   - Make multiline mode the only mode                                        │
+│   - Accommodate O_NONBLOCK file descriptors                                  │
+│   - Restore raw mode on process foregrounding                                │
+│   - Make source code compatible with C++ compilers                           │
+│   - Fix corruption issues by using generalized parsing                       │
+│   - Implement nearly all GNU readline editing shortcuts                      │
+│   - Remove heavyweight dependencies like printf/sprintf                      │
+│   - Remove ISIG→^C→EAGAIN hack and use ephemeral handlers                    │
+│   - Support running on Windows in MinTTY or CMD.EXE on Win10+                │
+│   - Support diacratics, русский, Ελληνικά, 中国人, 日本語, 한국인            │
+│                                                                              │
+│ SHORTCUTS                                                                    │
+│                                                                              │
+│                                                                              │
+│   CTRL-E         END                                                         │
+│   CTRL-A         START                                                       │
+│   CTRL-B         BACK                                                        │
+│   CTRL-F         FORWARD                                                     │
+│   CTRL-L         CLEAR                                                       │
+│   CTRL-H         BACKSPACE                                                   │
+│   CTRL-D         DELETE                                                      │
+│   CTRL-D         EOF (IF EMPTY)                                              │
+│   CTRL-N         NEXT HISTORY                                                │
+│   CTRL-P         PREVIOUS HISTORY                                            │
+│   CTRL-R         SEARCH HISTORY                                              │
+│   CTRL-G         CANCEL SEARCH                                               │
+│   ALT-<          BEGINNING OF HISTORY                                        │
+│   ALT->          END OF HISTORY                                              │
+│   ALT-F          FORWARD WORD                                                │
+│   ALT-B          BACKWARD WORD                                               │
+│   CTRL-ALT-F     FORWARD EXPR                                                │
+│   CTRL-ALT-B     BACKWARD EXPR                                               │
+│   CTRL-K         KILL LINE FORWARDS                                          │
+│   CTRL-U         KILL LINE BACKWARDS                                         │
+│   ALT-H          KILL WORD BACKWARDS                                         │
+│   CTRL-W         KILL WORD BACKWARDS                                         │
+│   CTRL-ALT-H     KILL WORD BACKWARDS                                         │
+│   ALT-D          KILL WORD FORWARDS                                          │
+│   CTRL-Y         YANK                                                        │
+│   ALT-Y          ROTATE KILL RING AND YANK AGAIN                             │
+│   CTRL-T         TRANSPOSE                                                   │
+│   ALT-T          TRANSPOSE WORD                                              │
+│   ALT-U          UPPERCASE WORD                                              │
+│   ALT-L          LOWERCASE WORD                                              │
+│   ALT-C          CAPITALIZE WORD                                             │
+│   CTRL-C         INTERRUPT PROCESS                                           │
+│   CTRL-Z         SUSPEND PROCESS                                             │
+│   CTRL-\         QUIT PROCESS                                                │
+│   CTRL-S         PAUSE OUTPUT                                                │
+│   CTRL-Q         UNPAUSE OUTPUT (IF PAUSED)                                  │
+│   CTRL-Q         ESCAPED INSERT                                              │
+│   CTRL-SPACE     SET MARK                                                    │
+│   CTRL-X CTRL-X  GOTO MARK                                                   │
+│   PROTIP         REMAP CAPS LOCK TO CTRL                                     │
+│                                                                              │
+╞══════════════════════════════════════════════════════════════════════════════╡
+│                                                                              │
+│ Copyright 2018-2021 Justine Tunney <jtunney@gmail.com>                       │
+│ Copyright 2010-2016 Salvatore Sanfilippo <antirez@gmail.com>                 │
+│ Copyright 2010-2013 Pieter Noordhuis <pcnoordhuis@gmail.com>                 │
+│                                                                              │
+│ All rights reserved.                                                         │
+│                                                                              │
+│ Redistribution and use in source and binary forms, with or without           │
+│ modification, are permitted provided that the following conditions are       │
+│ met:                                                                         │
+│                                                                              │
+│  *  Redistributions of source code must retain the above copyright           │
+│     notice, this list of conditions and the following disclaimer.            │
+│                                                                              │
+│  *  Redistributions in binary form must reproduce the above copyright        │
+│     notice, this list of conditions and the following disclaimer in the      │
+│     documentation and/or other materials provided with the distribution.     │
+│                                                                              │
+│ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS          │
+│ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT            │
+│ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR        │
+│ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT         │
+│ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,       │
+│ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT             │
+│ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,        │
+│ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY        │
+│ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT          │
+│ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE        │
+│ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         │
+│                                                                              │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "bestline.h"
+
+#ifndef __COSMOPOLITAN__
+#define _POSIX_C_SOURCE  1 /* so GCC builds in ANSI mode */
+#define _XOPEN_SOURCE  700 /* so GCC builds in ANSI mode */
+#define _DARWIN_C_SOURCE 1 /* so SIGWINCH / IUTF8 on XNU */
+#include <termios.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <setjmp.h>
+#include <poll.h>
+#include <assert.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <limits.h>
+#ifndef SIGWINCH
+#define SIGWINCH 28 /* GNU/Systemd + XNU + FreeBSD + NetBSD + OpenBSD */
+#endif
+#ifndef IUTF8
+#define IUTF8 0
+#endif
+#endif
+
+__asm__(".ident\t\"\\n\\n\
+Bestline (BSD-2)\\n\
+Copyright 2018-2020 Justine Tunney <jtunney@gmail.com>\\n\
+Copyright 2010-2016 Salvatore Sanfilippo <antirez@gmail.com>\\n\
+Copyright 2010-2013 Pieter Noordhuis <pcnoordhuis@gmail.com>\"");
+
+#ifndef BESTLINE_MAX_RING
+#define BESTLINE_MAX_RING 8
+#endif
+
+#ifndef BESTLINE_MAX_HISTORY
+#define BESTLINE_MAX_HISTORY 1024
+#endif
+
+#define BESTLINE_HISTORY_FIRST +BESTLINE_MAX_HISTORY
+#define BESTLINE_HISTORY_PREV  +1
+#define BESTLINE_HISTORY_NEXT  -1
+#define BESTLINE_HISTORY_LAST  -BESTLINE_MAX_HISTORY
+
+#define Ctrl(C)    ((C) ^ 0100)
+#define Min(X, Y)  ((Y) > (X) ? (X) : (Y))
+#define Max(X, Y)  ((Y) < (X) ? (X) : (Y))
+#define Case(X, Y) case X: Y; break
+#define Read16le(X)        \
+  ((255 & (X)[0]) << 000 | \
+   (255 & (X)[1]) << 010)
+#define Read32le(X)                  \
+  ((unsigned)(255 & (X)[0]) << 000 | \
+   (unsigned)(255 & (X)[1]) << 010 | \
+   (unsigned)(255 & (X)[2]) << 020 | \
+   (unsigned)(255 & (X)[3]) << 030)
+
+struct abuf {
+    char *b;
+    unsigned len;
+    unsigned cap;
+};
+
+struct rune {
+    unsigned c;
+    unsigned n;
+};
+
+struct bestlineRing {
+    unsigned i;
+    char *p[BESTLINE_MAX_RING];
+};
+
+/* The bestlineState structure represents the state during line editing.
+ * We pass this state to functions implementing specific editing
+ * functionalities. */
+struct bestlineState {
+    int ifd;            /* terminal stdin file descriptor */
+    int ofd;            /* terminal stdout file descriptor */
+    struct winsize ws;  /* rows and columns in terminal */
+    char *buf;          /* edited line buffer */
+    const char *prompt; /* prompt to display */
+    int hindex;         /* history index */
+    int rows;           /* rows being used */
+    int oldpos;         /* previous refresh cursor position */
+    unsigned buflen;    /* edited line buffer size */
+    unsigned pos;       /* current buffer index */
+    unsigned len;       /* current edited line length */
+    unsigned mark;      /* saved cursor position */
+    unsigned yi, yj;    /* boundaries of last yank */
+    char seq[2][16];    /* keystroke history for yanking code */
+    char final;         /* set to true on last update */
+    char dirty;         /* if an update was squashed */
+};
+
+static const char *const kUnsupported[] = {"dumb","cons25","emacs"};
+
+static int gotint;
+static int gotcont;
+static int gotwinch;
+static char rawmode;
+static char maskmode;
+static char ispaused;
+static char iscapital;
+static unsigned historylen;
+static struct bestlineRing ring;
+static struct sigaction orig_cont;
+static struct sigaction orig_winch;
+static struct termios orig_termios;
+static char *history[BESTLINE_MAX_HISTORY];
+static unsigned (*xlatCallback)(unsigned);
+static bestlineHintsCallback *hintsCallback;
+static bestlineFreeHintsCallback *freeHintsCallback;
+static bestlineCompletionCallback *completionCallback;
+
+static void bestlineAtExit(void);
+static void bestlineRefreshLine(struct bestlineState *);
+
+static void bestlineOnInt(int sig) {
+    gotint = sig;
+}
+
+static void bestlineOnCont(int sig) {
+    gotcont = sig;
+}
+
+static void bestlineOnWinch(int sig) {
+    gotwinch = sig;
+}
+
+static char IsControl(unsigned c) {
+    return c <= 0x1F || (0x7F <= c && c <= 0x9F);
+}
+
+static int GetMonospaceCharacterWidth(unsigned c) {
+    return !IsControl(c)
+            + (c >= 0x1100 &&
+               (c <= 0x115f || c == 0x2329 || c == 0x232a ||
+                (c >= 0x2e80 && c <= 0xa4cf && c != 0x303f) ||
+                (c >= 0xac00 && c <= 0xd7a3) ||
+                (c >= 0xf900 && c <= 0xfaff) ||
+                (c >= 0xfe10 && c <= 0xfe19) ||
+                (c >= 0xfe30 && c <= 0xfe6f) ||
+                (c >= 0xff00 && c <= 0xff60) ||
+                (c >= 0xffe0 && c <= 0xffe6) ||
+                (c >= 0x20000 && c <= 0x2fffd) ||
+                (c >= 0x30000 && c <= 0x3fffd)));
+}
+
+/**
+ * Returns nonzero if 𝑐 isn't alphanumeric.
+ *
+ * Line reading interfaces generally define this operation as UNICODE
+ * characters that aren't in the letter category (Lu, Ll, Lt, Lm, Lo)
+ * and aren't in the number categorie (Nd, Nl, No). We also add a few
+ * other things like blocks and emoji (So).
+ */
+static char IsSeparator(unsigned c) {
+    int m, l, r, n;
+    if (c < 0200) {
+        return !(('0' <= c && c <= '9') ||
+                 ('A' <= c && c <= 'Z') ||
+                 ('a' <= c && c <= 'z'));
+    }
+    if (c <= 0xffff) {
+        static const unsigned short kGlyphs[][2] = {
+            {0x00aa, 0x00aa}, /*     1x English */
+            {0x00b2, 0x00b3}, /*     2x English Arabic */
+            {0x00b5, 0x00b5}, /*     1x Greek */
+            {0x00b9, 0x00ba}, /*     2x English Arabic */
+            {0x00bc, 0x00be}, /*     3x Vulgar English Arabic */
+            {0x00c0, 0x00d6}, /*    23x Watin */
+            {0x00d8, 0x00f6}, /*    31x Watin */
+            {0x0100, 0x02c1}, /*   450x Watin-AB,IPA,Spacemod */
+            {0x02c6, 0x02d1}, /*    12x Spacemod */
+            {0x02e0, 0x02e4}, /*     5x Spacemod */
+            {0x02ec, 0x02ec}, /*     1x Spacemod */
+            {0x02ee, 0x02ee}, /*     1x Spacemod */
+            {0x0370, 0x0374}, /*     5x Greek */
+            {0x0376, 0x0377}, /*     2x Greek */
+            {0x037a, 0x037d}, /*     4x Greek */
+            {0x037f, 0x037f}, /*     1x Greek */
+            {0x0386, 0x0386}, /*     1x Greek */
+            {0x0388, 0x038a}, /*     3x Greek */
+            {0x038c, 0x038c}, /*     1x Greek */
+            {0x038e, 0x03a1}, /*    20x Greek */
+            {0x03a3, 0x03f5}, /*    83x Greek */
+            {0x03f7, 0x0481}, /*   139x Greek */
+            {0x048a, 0x052f}, /*   166x Cyrillic */
+            {0x0531, 0x0556}, /*    38x Armenian */
+            {0x0560, 0x0588}, /*    41x Armenian */
+            {0x05d0, 0x05ea}, /*    27x Hebrew */
+            {0x0620, 0x064a}, /*    43x Arabic */
+            {0x0660, 0x0669}, /*    10x Arabic */
+            {0x0671, 0x06d3}, /*    99x Arabic */
+            {0x06ee, 0x06fc}, /*    15x Arabic */
+            {0x0712, 0x072f}, /*    30x Syriac */
+            {0x074d, 0x07a5}, /*    89x Syriac,Arabic2,Thaana */
+            {0x07c0, 0x07ea}, /*    43x NKo */
+            {0x0800, 0x0815}, /*    22x Samaritan */
+            {0x0840, 0x0858}, /*    25x Mandaic */
+            {0x0904, 0x0939}, /*    54x Devanagari */
+            {0x0993, 0x09a8}, /*    22x Bengali */
+            {0x09e6, 0x09f1}, /*    12x Bengali */
+            {0x0a13, 0x0a28}, /*    22x Gurmukhi */
+            {0x0a66, 0x0a6f}, /*    10x Gurmukhi */
+            {0x0a93, 0x0aa8}, /*    22x Gujarati */
+            {0x0b13, 0x0b28}, /*    22x Oriya */
+            {0x0c92, 0x0ca8}, /*    23x Kannada */
+            {0x0caa, 0x0cb3}, /*    10x Kannada */
+            {0x0ce6, 0x0cef}, /*    10x Kannada */
+            {0x0d12, 0x0d3a}, /*    41x Malayalam */
+            {0x0d85, 0x0d96}, /*    18x Sinhala */
+            {0x0d9a, 0x0db1}, /*    24x Sinhala */
+            {0x0de6, 0x0def}, /*    10x Sinhala */
+            {0x0e01, 0x0e30}, /*    48x Thai */
+            {0x0e8c, 0x0ea3}, /*    24x Lao */
+            {0x0f20, 0x0f33}, /*    20x Tibetan */
+            {0x0f49, 0x0f6c}, /*    36x Tibetan */
+            {0x109e, 0x10c5}, /*    40x Myanmar,Georgian */
+            {0x10d0, 0x10fa}, /*    43x Georgian */
+            {0x10fc, 0x1248}, /*   333x Georgian,Hangul,Ethiopic */
+            {0x13a0, 0x13f5}, /*    86x Cherokee */
+            {0x1401, 0x166d}, /*   621x Aboriginal */
+            {0x16a0, 0x16ea}, /*    75x Runic */
+            {0x1700, 0x170c}, /*    13x Tagalog */
+            {0x1780, 0x17b3}, /*    52x Khmer */
+            {0x1820, 0x1878}, /*    89x Mongolian */
+            {0x1a00, 0x1a16}, /*    23x Buginese */
+            {0x1a20, 0x1a54}, /*    53x Tai Tham */
+            {0x1a80, 0x1a89}, /*    10x Tai Tham */
+            {0x1a90, 0x1a99}, /*    10x Tai Tham */
+            {0x1b05, 0x1b33}, /*    47x Balinese */
+            {0x1b50, 0x1b59}, /*    10x Balinese */
+            {0x1b83, 0x1ba0}, /*    30x Sundanese */
+            {0x1bae, 0x1be5}, /*    56x Sundanese */
+            {0x1c90, 0x1cba}, /*    43x Georgian2 */
+            {0x1cbd, 0x1cbf}, /*     3x Georgian2 */
+            {0x1e00, 0x1f15}, /*   278x Watin-C,Greek2 */
+            {0x2070, 0x2071}, /*     2x Supersub */
+            {0x2074, 0x2079}, /*     6x Supersub */
+            {0x207f, 0x2089}, /*    11x Supersub */
+            {0x2090, 0x209c}, /*    13x Supersub */
+            {0x2100, 0x2117}, /*    24x Letterlike */
+            {0x2119, 0x213f}, /*    39x Letterlike */
+            {0x2145, 0x214a}, /*     6x Letterlike */
+            {0x214c, 0x218b}, /*    64x Letterlike,Numbery */
+            {0x21af, 0x21cd}, /*    31x Arrows */
+            {0x21d5, 0x21f3}, /*    31x Arrows */
+            {0x230c, 0x231f}, /*    20x Technical */
+            {0x232b, 0x237b}, /*    81x Technical */
+            {0x237d, 0x239a}, /*    30x Technical */
+            {0x23b4, 0x23db}, /*    40x Technical */
+            {0x23e2, 0x2426}, /*    69x Technical,ControlPictures */
+            {0x2460, 0x25b6}, /*   343x Enclosed,Boxes,Blocks,Shapes */
+            {0x25c2, 0x25f7}, /*    54x Shapes */
+            {0x2600, 0x266e}, /*   111x Symbols */
+            {0x2670, 0x2767}, /*   248x Symbols,Dingbats */
+            {0x2776, 0x27bf}, /*    74x Dingbats */
+            {0x2800, 0x28ff}, /*   256x Braille */
+            {0x2c00, 0x2c2e}, /*    47x Glagolitic */
+            {0x2c30, 0x2c5e}, /*    47x Glagolitic */
+            {0x2c60, 0x2ce4}, /*   133x Watin-D */
+            {0x2d00, 0x2d25}, /*    38x Georgian2 */
+            {0x2d30, 0x2d67}, /*    56x Tifinagh */
+            {0x2d80, 0x2d96}, /*    23x Ethiopic2 */
+            {0x2e2f, 0x2e2f}, /*     1x Punctuation2 */
+            {0x3005, 0x3007}, /*     3x CJK Symbols & Punctuation */
+            {0x3021, 0x3029}, /*     9x CJK Symbols & Punctuation */
+            {0x3031, 0x3035}, /*     5x CJK Symbols & Punctuation */
+            {0x3038, 0x303c}, /*     5x CJK Symbols & Punctuation */
+            {0x3041, 0x3096}, /*    86x Hiragana */
+            {0x30a1, 0x30fa}, /*    90x Katakana */
+            {0x3105, 0x312f}, /*    43x Bopomofo */
+            {0x3131, 0x318e}, /*    94x Hangul Compatibility Jamo */
+            {0x31a0, 0x31ba}, /*    27x Bopomofo Extended */
+            {0x31f0, 0x31ff}, /*    16x Katakana Phonetic Extensions */
+            {0x3220, 0x3229}, /*    10x Enclosed CJK Letters & Months */
+            {0x3248, 0x324f}, /*     8x Enclosed CJK Letters & Months */
+            {0x3251, 0x325f}, /*    15x Enclosed CJK Letters & Months */
+            {0x3280, 0x3289}, /*    10x Enclosed CJK Letters & Months */
+            {0x32b1, 0x32bf}, /*    15x Enclosed CJK Letters & Months */
+            {0x3400, 0x4db5}, /*  6582x CJK Unified Ideographs Extension A */
+            {0x4dc0, 0x9fef}, /* 21040x Yijing Hexagram, CJK Unified Ideographs */
+            {0xa000, 0xa48c}, /*  1165x Yi Syllables */
+            {0xa4d0, 0xa4fd}, /*    46x Lisu */
+            {0xa500, 0xa60c}, /*   269x Vai */
+            {0xa610, 0xa62b}, /*    28x Vai */
+            {0xa6a0, 0xa6ef}, /*    80x Bamum */
+            {0xa80c, 0xa822}, /*    23x Syloti Nagri */
+            {0xa840, 0xa873}, /*    52x Phags-pa */
+            {0xa882, 0xa8b3}, /*    50x Saurashtra */
+            {0xa8d0, 0xa8d9}, /*    10x Saurashtra */
+            {0xa900, 0xa925}, /*    38x Kayah Li */
+            {0xa930, 0xa946}, /*    23x Rejang */
+            {0xa960, 0xa97c}, /*    29x Hangul Jamo Extended-A */
+            {0xa984, 0xa9b2}, /*    47x Javanese */
+            {0xa9cf, 0xa9d9}, /*    11x Javanese */
+            {0xaa00, 0xaa28}, /*    41x Cham */
+            {0xaa50, 0xaa59}, /*    10x Cham */
+            {0xabf0, 0xabf9}, /*    10x Meetei Mayek */
+            {0xac00, 0xd7a3}, /* 11172x Hangul Syllables */
+            {0xf900, 0xfa6d}, /*   366x CJK Compatibility Ideographs */
+            {0xfa70, 0xfad9}, /*   106x CJK Compatibility Ideographs */
+            {0xfb1f, 0xfb28}, /*    10x Alphabetic Presentation Forms */
+            {0xfb2a, 0xfb36}, /*    13x Alphabetic Presentation Forms */
+            {0xfb46, 0xfbb1}, /*   108x Alphabetic Presentation Forms */
+            {0xfbd3, 0xfd3d}, /*   363x Arabic Presentation Forms-A */
+            {0xfe76, 0xfefc}, /*   135x Arabic Presentation Forms-B */
+            {0xff10, 0xff19}, /*    10x Dubs */
+            {0xff21, 0xff3a}, /*    26x Dubs */
+            {0xff41, 0xff5a}, /*    26x Dubs */
+            {0xff66, 0xffbe}, /*    89x Dubs */
+            {0xffc2, 0xffc7}, /*     6x Dubs */
+            {0xffca, 0xffcf}, /*     6x Dubs */
+            {0xffd2, 0xffd7}, /*     6x Dubs */
+            {0xffda, 0xffdc}, /*     3x Dubs */
+        };
+        l = 0;
+        r = n = sizeof(kGlyphs) / sizeof(kGlyphs[0]);
+        while (l < r) {
+            m = (l + r) >> 1;
+            if (kGlyphs[m][1] < c) {
+                l = m + 1;
+            } else {
+                r = m;
+            }
+        }
+        return !(l < n && kGlyphs[l][0] <= c && c <= kGlyphs[l][1]);
+    } else {
+        static const unsigned kAstralGlyphs[][2] = {
+            {0x10107, 0x10133}, /*    45x Aegean */
+            {0x10140, 0x10178}, /*    57x Ancient Greek Numbers */
+            {0x1018a, 0x1018b}, /*     2x Ancient Greek Numbers */
+            {0x10280, 0x1029c}, /*    29x Lycian */
+            {0x102a0, 0x102d0}, /*    49x Carian */
+            {0x102e1, 0x102fb}, /*    27x Coptic Epact Numbers */
+            {0x10300, 0x10323}, /*    36x Old Italic */
+            {0x1032d, 0x1034a}, /*    30x Old Italic, Gothic */
+            {0x10350, 0x10375}, /*    38x Old Permic */
+            {0x10380, 0x1039d}, /*    30x Ugaritic */
+            {0x103a0, 0x103c3}, /*    36x Old Persian */
+            {0x103c8, 0x103cf}, /*     8x Old Persian */
+            {0x103d1, 0x103d5}, /*     5x Old Persian */
+            {0x10400, 0x1049d}, /*    158x Deseret, Shavian, Osmanya */
+            {0x104b0, 0x104d3}, /*    36x Osage */
+            {0x104d8, 0x104fb}, /*    36x Osage */
+            {0x10500, 0x10527}, /*    40x Elbasan */
+            {0x10530, 0x10563}, /*    52x Caucasian Albanian */
+            {0x10600, 0x10736}, /*   311x Linear A */
+            {0x10800, 0x10805}, /*     6x Cypriot Syllabary */
+            {0x1080a, 0x10835}, /*    44x Cypriot Syllabary */
+            {0x10837, 0x10838}, /*     2x Cypriot Syllabary */
+            {0x1083f, 0x1089e}, /*    86x Cypriot,ImperialAramaic,Palmyrene,Nabataean */
+            {0x108e0, 0x108f2}, /*    19x Hatran */
+            {0x108f4, 0x108f5}, /*     2x Hatran */
+            {0x108fb, 0x1091b}, /*    33x Hatran */
+            {0x10920, 0x10939}, /*    26x Lydian */
+            {0x10980, 0x109b7}, /*    56x Meroitic Hieroglyphs */
+            {0x109bc, 0x109cf}, /*    20x Meroitic Cursive */
+            {0x109d2, 0x10a00}, /*    47x Meroitic Cursive */
+            {0x10a10, 0x10a13}, /*     4x Kharoshthi */
+            {0x10a15, 0x10a17}, /*     3x Kharoshthi */
+            {0x10a19, 0x10a35}, /*    29x Kharoshthi */
+            {0x10a40, 0x10a48}, /*     9x Kharoshthi */
+            {0x10a60, 0x10a7e}, /*    31x Old South Arabian */
+            {0x10a80, 0x10a9f}, /*    32x Old North Arabian */
+            {0x10ac0, 0x10ac7}, /*     8x Manichaean */
+            {0x10ac9, 0x10ae4}, /*    28x Manichaean */
+            {0x10aeb, 0x10aef}, /*     5x Manichaean */
+            {0x10b00, 0x10b35}, /*    54x Avestan */
+            {0x10b40, 0x10b55}, /*    22x Inscriptional Parthian */
+            {0x10b58, 0x10b72}, /*    27x Inscriptional Parthian and Pahlavi */
+            {0x10b78, 0x10b91}, /*    26x Inscriptional Pahlavi, Psalter Pahlavi */
+            {0x10c00, 0x10c48}, /*    73x Old Turkic */
+            {0x10c80, 0x10cb2}, /*    51x Old Hungarian */
+            {0x10cc0, 0x10cf2}, /*    51x Old Hungarian */
+            {0x10cfa, 0x10d23}, /*    42x Old Hungarian, Hanifi Rohingya */
+            {0x10d30, 0x10d39}, /*    10x Hanifi Rohingya */
+            {0x10e60, 0x10e7e}, /*    31x Rumi Numeral Symbols */
+            {0x10f00, 0x10f27}, /*    40x Old Sogdian */
+            {0x10f30, 0x10f45}, /*    22x Sogdian */
+            {0x10f51, 0x10f54}, /*     4x Sogdian */
+            {0x10fe0, 0x10ff6}, /*    23x Elymaic */
+            {0x11003, 0x11037}, /*    53x Brahmi */
+            {0x11052, 0x1106f}, /*    30x Brahmi */
+            {0x11083, 0x110af}, /*    45x Kaithi */
+            {0x110d0, 0x110e8}, /*    25x Sora Sompeng */
+            {0x110f0, 0x110f9}, /*    10x Sora Sompeng */
+            {0x11103, 0x11126}, /*    36x Chakma */
+            {0x11136, 0x1113f}, /*    10x Chakma */
+            {0x11144, 0x11144}, /*     1x Chakma */
+            {0x11150, 0x11172}, /*    35x Mahajani */
+            {0x11176, 0x11176}, /*     1x Mahajani */
+            {0x11183, 0x111b2}, /*    48x Sharada */
+            {0x111c1, 0x111c4}, /*     4x Sharada */
+            {0x111d0, 0x111da}, /*    11x Sharada */
+            {0x111dc, 0x111dc}, /*     1x Sharada */
+            {0x111e1, 0x111f4}, /*    20x Sinhala Archaic Numbers */
+            {0x11200, 0x11211}, /*    18x Khojki */
+            {0x11213, 0x1122b}, /*    25x Khojki */
+            {0x11280, 0x11286}, /*     7x Multani */
+            {0x11288, 0x11288}, /*     1x Multani */
+            {0x1128a, 0x1128d}, /*     4x Multani */
+            {0x1128f, 0x1129d}, /*    15x Multani */
+            {0x1129f, 0x112a8}, /*    10x Multani */
+            {0x112b0, 0x112de}, /*    47x Khudawadi */
+            {0x112f0, 0x112f9}, /*    10x Khudawadi */
+            {0x11305, 0x1130c}, /*     8x Grantha */
+            {0x1130f, 0x11310}, /*     2x Grantha */
+            {0x11313, 0x11328}, /*    22x Grantha */
+            {0x1132a, 0x11330}, /*     7x Grantha */
+            {0x11332, 0x11333}, /*     2x Grantha */
+            {0x11335, 0x11339}, /*     5x Grantha */
+            {0x1133d, 0x1133d}, /*     1x Grantha */
+            {0x11350, 0x11350}, /*     1x Grantha */
+            {0x1135d, 0x11361}, /*     5x Grantha */
+            {0x11400, 0x11434}, /*    53x Newa */
+            {0x11447, 0x1144a}, /*     4x Newa */
+            {0x11450, 0x11459}, /*    10x Newa */
+            {0x1145f, 0x1145f}, /*     1x Newa */
+            {0x11480, 0x114af}, /*    48x Tirhuta */
+            {0x114c4, 0x114c5}, /*     2x Tirhuta */
+            {0x114c7, 0x114c7}, /*     1x Tirhuta */
+            {0x114d0, 0x114d9}, /*    10x Tirhuta */
+            {0x11580, 0x115ae}, /*    47x Siddham */
+            {0x115d8, 0x115db}, /*     4x Siddham */
+            {0x11600, 0x1162f}, /*    48x Modi */
+            {0x11644, 0x11644}, /*     1x Modi */
+            {0x11650, 0x11659}, /*    10x Modi */
+            {0x11680, 0x116aa}, /*    43x Takri */
+            {0x116b8, 0x116b8}, /*     1x Takri */
+            {0x116c0, 0x116c9}, /*    10x Takri */
+            {0x11700, 0x1171a}, /*    27x Ahom */
+            {0x11730, 0x1173b}, /*    12x Ahom */
+            {0x11800, 0x1182b}, /*    44x Dogra */
+            {0x118a0, 0x118f2}, /*    83x Warang Citi */
+            {0x118ff, 0x118ff}, /*     1x Warang Citi */
+            {0x119a0, 0x119a7}, /*     8x Nandinagari */
+            {0x119aa, 0x119d0}, /*    39x Nandinagari */
+            {0x119e1, 0x119e1}, /*     1x Nandinagari */
+            {0x119e3, 0x119e3}, /*     1x Nandinagari */
+            {0x11a00, 0x11a00}, /*     1x Zanabazar Square */
+            {0x11a0b, 0x11a32}, /*    40x Zanabazar Square */
+            {0x11a3a, 0x11a3a}, /*     1x Zanabazar Square */
+            {0x11a50, 0x11a50}, /*     1x Soyombo */
+            {0x11a5c, 0x11a89}, /*    46x Soyombo */
+            {0x11a9d, 0x11a9d}, /*     1x Soyombo */
+            {0x11ac0, 0x11af8}, /*    57x Pau Cin Hau */
+            {0x11c00, 0x11c08}, /*     9x Bhaiksuki */
+            {0x11c0a, 0x11c2e}, /*    37x Bhaiksuki */
+            {0x11c40, 0x11c40}, /*     1x Bhaiksuki */
+            {0x11c50, 0x11c6c}, /*    29x Bhaiksuki */
+            {0x11c72, 0x11c8f}, /*    30x Marchen */
+            {0x11d00, 0x11d06}, /*     7x Masaram Gondi */
+            {0x11d08, 0x11d09}, /*     2x Masaram Gondi */
+            {0x11d0b, 0x11d30}, /*    38x Masaram Gondi */
+            {0x11d46, 0x11d46}, /*     1x Masaram Gondi */
+            {0x11d50, 0x11d59}, /*    10x Masaram Gondi */
+            {0x11d60, 0x11d65}, /*     6x Gunjala Gondi */
+            {0x11d67, 0x11d68}, /*     2x Gunjala Gondi */
+            {0x11d6a, 0x11d89}, /*    32x Gunjala Gondi */
+            {0x11d98, 0x11d98}, /*     1x Gunjala Gondi */
+            {0x11da0, 0x11da9}, /*    10x Gunjala Gondi */
+            {0x11ee0, 0x11ef2}, /*    19x Makasar */
+            {0x11fc0, 0x11fd4}, /*    21x Tamil Supplement */
+            {0x12000, 0x12399}, /*   922x Cuneiform */
+            {0x12400, 0x1246e}, /*   111x Cuneiform Numbers & Punctuation */
+            {0x12480, 0x12543}, /*   196x Early Dynastic Cuneiform */
+            {0x13000, 0x1342e}, /*  1071x Egyptian Hieroglyphs */
+            {0x14400, 0x14646}, /*   583x Anatolian Hieroglyphs */
+            {0x16800, 0x16a38}, /*   569x Bamum Supplement */
+            {0x16a40, 0x16a5e}, /*    31x Mro */
+            {0x16a60, 0x16a69}, /*    10x Mro */
+            {0x16ad0, 0x16aed}, /*    30x Bassa Vah */
+            {0x16b00, 0x16b2f}, /*    48x Pahawh Hmong */
+            {0x16b40, 0x16b43}, /*     4x Pahawh Hmong */
+            {0x16b50, 0x16b59}, /*    10x Pahawh Hmong */
+            {0x16b5b, 0x16b61}, /*     7x Pahawh Hmong */
+            {0x16b63, 0x16b77}, /*    21x Pahawh Hmong */
+            {0x16b7d, 0x16b8f}, /*    19x Pahawh Hmong */
+            {0x16e40, 0x16e96}, /*    87x Medefaidrin */
+            {0x16f00, 0x16f4a}, /*    75x Miao */
+            {0x16f50, 0x16f50}, /*     1x Miao */
+            {0x16f93, 0x16f9f}, /*    13x Miao */
+            {0x16fe0, 0x16fe1}, /*     2x Ideographic Symbols & Punctuation */
+            {0x16fe3, 0x16fe3}, /*     1x Ideographic Symbols & Punctuation */
+            {0x17000, 0x187f7}, /*  6136x Tangut */
+            {0x18800, 0x18af2}, /*   755x Tangut Components */
+            {0x1b000, 0x1b11e}, /*   287x Kana Supplement */
+            {0x1b150, 0x1b152}, /*     3x Small Kana Extension */
+            {0x1b164, 0x1b167}, /*     4x Small Kana Extension */
+            {0x1b170, 0x1b2fb}, /*   396x Nushu */
+            {0x1bc00, 0x1bc6a}, /*   107x Duployan */
+            {0x1bc70, 0x1bc7c}, /*    13x Duployan */
+            {0x1bc80, 0x1bc88}, /*     9x Duployan */
+            {0x1bc90, 0x1bc99}, /*    10x Duployan */
+            {0x1d2e0, 0x1d2f3}, /*    20x Mayan Numerals */
+            {0x1d360, 0x1d378}, /*    25x Counting Rod Numerals */
+            {0x1d400, 0x1d454}, /*    85x 𝐀..𝑔 Math */
+            {0x1d456, 0x1d49c}, /*    71x 𝑖..𝒜 Math */
+            {0x1d49e, 0x1d49f}, /*     2x 𝒞..𝒟 Math */
+            {0x1d4a2, 0x1d4a2}, /*     1x 𝒢..𝒢 Math */
+            {0x1d4a5, 0x1d4a6}, /*     2x 𝒥..𝒦 Math */
+            {0x1d4a9, 0x1d4ac}, /*     4x 𝒩..𝒬 Math */
+            {0x1d4ae, 0x1d4b9}, /*    12x 𝒮..𝒹 Math */
+            {0x1d4bb, 0x1d4bb}, /*     1x 𝒻..𝒻 Math */
+            {0x1d4bd, 0x1d4c3}, /*     7x 𝒽..𝓃 Math */
+            {0x1d4c5, 0x1d505}, /*    65x 𝓅..𝔅 Math */
+            {0x1d507, 0x1d50a}, /*     4x 𝔇..𝔊 Math */
+            {0x1d50d, 0x1d514}, /*     8x 𝔍..𝔔 Math */
+            {0x1d516, 0x1d51c}, /*     7x 𝔖..𝔜 Math */
+            {0x1d51e, 0x1d539}, /*    28x 𝔞..𝔹 Math */
+            {0x1d53b, 0x1d53e}, /*     4x 𝔻..𝔾 Math */
+            {0x1d540, 0x1d544}, /*     5x 𝕀..𝕄 Math */
+            {0x1d546, 0x1d546}, /*     1x 𝕆..𝕆 Math */
+            {0x1d54a, 0x1d550}, /*     7x 𝕊..𝕐 Math */
+            {0x1d552, 0x1d6a5}, /*   340x 𝕒..𝚥 Math */
+            {0x1d6a8, 0x1d6c0}, /*    25x 𝚨..𝛀 Math */
+            {0x1d6c2, 0x1d6da}, /*    25x 𝛂..𝛚 Math */
+            {0x1d6dc, 0x1d6fa}, /*    31x 𝛜..𝛺 Math */
+            {0x1d6fc, 0x1d714}, /*    25x 𝛼..𝜔 Math */
+            {0x1d716, 0x1d734}, /*    31x 𝜖..𝜴 Math */
+            {0x1d736, 0x1d74e}, /*    25x 𝜶..𝝎 Math */
+            {0x1d750, 0x1d76e}, /*    31x 𝝐..𝝮 Math */
+            {0x1d770, 0x1d788}, /*    25x 𝝰..𝞈 Math */
+            {0x1d78a, 0x1d7a8}, /*    31x 𝞊..𝞨 Math */
+            {0x1d7aa, 0x1d7c2}, /*    25x 𝞪..𝟂 Math */
+            {0x1d7c4, 0x1d7cb}, /*     8x 𝟄..𝟋 Math */
+            {0x1d7ce, 0x1d9ff}, /*   562x Math, Sutton SignWriting */
+            {0x1f100, 0x1f10c}, /*    13x Enclosed Alphanumeric Supplement */
+            {0x20000, 0x2a6d6}, /* 42711x CJK Unified Ideographs Extension B */
+            {0x2a700, 0x2b734}, /*  4149x CJK Unified Ideographs Extension C */
+            {0x2b740, 0x2b81d}, /*   222x CJK Unified Ideographs Extension D */
+            {0x2b820, 0x2cea1}, /*  5762x CJK Unified Ideographs Extension E */
+            {0x2ceb0, 0x2ebe0}, /*  7473x CJK Unified Ideographs Extension F */
+            {0x2f800, 0x2fa1d}, /*   542x CJK Compatibility Ideographs Supplement */
+        };
+        l = 0;
+        r = n = sizeof(kAstralGlyphs) / sizeof(kAstralGlyphs[0]);
+        while (l < r) {
+            m = (l + r) >> 1;
+            if (kAstralGlyphs[m][1] < c) {
+                l = m + 1;
+            } else {
+                r = m;
+            }
+        }
+        return !(l < n && kAstralGlyphs[l][0] <= c && c <= kAstralGlyphs[l][1]);
+    }
+}
+
+unsigned bestlineLowercase(unsigned c) {
+    int m, l, r, n;
+    if (c < 0200) {
+        if ('A' <= c && c <= 'Z') {
+            return c + 32;
+        } else {
+            return c;
+        }
+    } else if (c <= 0xffff) {
+        if ((0x0100 <= c && c <= 0x0176) || /* 60x Ā..ā → ā..ŵ Watin-A */
+            (0x01de <= c && c <= 0x01ee) || /*  9x Ǟ..Ǯ → ǟ..ǯ Watin-B */
+            (0x01f8 <= c && c <= 0x021e) || /* 20x Ǹ..Ȟ → ǹ..ȟ Watin-B */
+            (0x0222 <= c && c <= 0x0232) || /*  9x Ȣ..Ȳ → ȣ..ȳ Watin-B */
+            (0x1e00 <= c && c <= 0x1eff)) { /*256x Ḁ..Ỿ → ḁ..ỿ Watin-C */
+            if (c == 0x0130) return c - 199;
+            if (c == 0x1e9e) return c;
+            return c + (~c & 1);
+        } else if (0x01cf <= c && c <= 0x01db) {
+            return c + (c & 1); /* 7x Ǐ..Ǜ → ǐ..ǜ Watin-B */
+        } else if (0x13a0 <= c && c <= 0x13ef) {
+            return c + 38864; /* 80x Ꭰ ..Ꮿ  → ꭰ ..ꮿ  Cherokee */
+        } else {
+            static const struct {
+                unsigned short a;
+                unsigned short b;
+                short d;
+            } kLower[] = {
+                {0x00c0, 0x00d6,   +32}, /* 23x À ..Ö  → à ..ö  Watin */
+                {0x00d8, 0x00de,   +32}, /*  7x Ø ..Þ  → ø ..þ  Watin */
+                {0x0178, 0x0178,  -121}, /*  1x Ÿ ..Ÿ  → ÿ ..ÿ  Watin-A */
+                {0x0179, 0x0179,    +1}, /*  1x Ź ..Ź  → ź ..ź  Watin-A */
+                {0x017b, 0x017b,    +1}, /*  1x Ż ..Ż  → ż ..ż  Watin-A */
+                {0x017d, 0x017d,    +1}, /*  1x Ž ..Ž  → ž ..ž  Watin-A */
+                {0x0181, 0x0181,  +210}, /*  1x Ɓ ..Ɓ  → ɓ ..ɓ  Watin-B */
+                {0x0182, 0x0182,    +1}, /*  1x Ƃ ..Ƃ  → ƃ ..ƃ  Watin-B */
+                {0x0184, 0x0184,    +1}, /*  1x Ƅ ..Ƅ  → ƅ ..ƅ  Watin-B */
+                {0x0186, 0x0186,  +206}, /*  1x Ɔ ..Ɔ  → ɔ ..ɔ  Watin-B */
+                {0x0187, 0x0187,    +1}, /*  1x Ƈ ..Ƈ  → ƈ ..ƈ  Watin-B */
+                {0x0189, 0x018a,  +205}, /*  2x Ɖ ..Ɗ  → ɖ ..ɗ  Watin-B */
+                {0x018b, 0x018b,    +1}, /*  1x Ƌ ..Ƌ  → ƌ ..ƌ  Watin-B */
+                {0x018e, 0x018e,   +79}, /*  1x Ǝ ..Ǝ  → ǝ ..ǝ  Watin-B */
+                {0x018f, 0x018f,  +202}, /*  1x Ə ..Ə  → ə ..ə  Watin-B */
+                {0x0190, 0x0190,  +203}, /*  1x Ɛ ..Ɛ  → ɛ ..ɛ  Watin-B */
+                {0x0191, 0x0191,    +1}, /*  1x Ƒ ..Ƒ  → ƒ ..ƒ  Watin-B */
+                {0x0193, 0x0193,  +205}, /*  1x Ɠ ..Ɠ  → ɠ ..ɠ  Watin-B */
+                {0x0194, 0x0194,  +207}, /*  1x Ɣ ..Ɣ  → ɣ ..ɣ  Watin-B */
+                {0x0196, 0x0196,  +211}, /*  1x Ɩ ..Ɩ  → ɩ ..ɩ  Watin-B */
+                {0x0197, 0x0197,  +209}, /*  1x Ɨ ..Ɨ  → ɨ ..ɨ  Watin-B */
+                {0x0198, 0x0198,    +1}, /*  1x Ƙ ..Ƙ  → ƙ ..ƙ  Watin-B */
+                {0x019c, 0x019c,  +211}, /*  1x Ɯ ..Ɯ  → ɯ ..ɯ  Watin-B */
+                {0x019d, 0x019d,  +213}, /*  1x Ɲ ..Ɲ  → ɲ ..ɲ  Watin-B */
+                {0x019f, 0x019f,  +214}, /*  1x Ɵ ..Ɵ  → ɵ ..ɵ  Watin-B */
+                {0x01a0, 0x01a0,    +1}, /*  1x Ơ ..Ơ  → ơ ..ơ  Watin-B */
+                {0x01a2, 0x01a2,    +1}, /*  1x Ƣ ..Ƣ  → ƣ ..ƣ  Watin-B */
+                {0x01a4, 0x01a4,    +1}, /*  1x Ƥ ..Ƥ  → ƥ ..ƥ  Watin-B */
+                {0x01a6, 0x01a6,  +218}, /*  1x Ʀ ..Ʀ  → ʀ ..ʀ  Watin-B */
+                {0x01a7, 0x01a7,    +1}, /*  1x Ƨ ..Ƨ  → ƨ ..ƨ  Watin-B */
+                {0x01a9, 0x01a9,  +218}, /*  1x Ʃ ..Ʃ  → ʃ ..ʃ  Watin-B */
+                {0x01ac, 0x01ac,    +1}, /*  1x Ƭ ..Ƭ  → ƭ ..ƭ  Watin-B */
+                {0x01ae, 0x01ae,  +218}, /*  1x Ʈ ..Ʈ  → ʈ ..ʈ  Watin-B */
+                {0x01af, 0x01af,    +1}, /*  1x Ư ..Ư  → ư ..ư  Watin-B */
+                {0x01b1, 0x01b2,  +217}, /*  2x Ʊ ..Ʋ  → ʊ ..ʋ  Watin-B */
+                {0x01b3, 0x01b3,    +1}, /*  1x Ƴ ..Ƴ  → ƴ ..ƴ  Watin-B */
+                {0x01b5, 0x01b5,    +1}, /*  1x Ƶ ..Ƶ  → ƶ ..ƶ  Watin-B */
+                {0x01b7, 0x01b7,  +219}, /*  1x Ʒ ..Ʒ  → ʒ ..ʒ  Watin-B */
+                {0x01b8, 0x01b8,    +1}, /*  1x Ƹ ..Ƹ  → ƹ ..ƹ  Watin-B */
+                {0x01bc, 0x01bc,    +1}, /*  1x Ƽ ..Ƽ  → ƽ ..ƽ  Watin-B */
+                {0x01c4, 0x01c4,    +2}, /*  1x DŽ ..DŽ  → dž ..dž  Watin-B */
+                {0x01c5, 0x01c5,    +1}, /*  1x Dž ..Dž  → dž ..dž  Watin-B */
+                {0x01c7, 0x01c7,    +2}, /*  1x LJ ..LJ  → lj ..lj  Watin-B */
+                {0x01c8, 0x01c8,    +1}, /*  1x Lj ..Lj  → lj ..lj  Watin-B */
+                {0x01ca, 0x01ca,    +2}, /*  1x NJ ..NJ  → nj ..nj  Watin-B */
+                {0x01cb, 0x01cb,    +1}, /*  1x Nj ..Nj  → nj ..nj  Watin-B */
+                {0x01cd, 0x01cd,    +1}, /*  1x Ǎ ..Ǎ  → ǎ ..ǎ  Watin-B */
+                {0x01f1, 0x01f1,    +2}, /*  1x DZ ..DZ  → dz ..dz  Watin-B */
+                {0x01f2, 0x01f2,    +1}, /*  1x Dz ..Dz  → dz ..dz  Watin-B */
+                {0x01f4, 0x01f4,    +1}, /*  1x Ǵ ..Ǵ  → ǵ ..ǵ  Watin-B */
+                {0x01f6, 0x01f6,   -97}, /*  1x Ƕ ..Ƕ  → ƕ ..ƕ  Watin-B */
+                {0x01f7, 0x01f7,   -56}, /*  1x Ƿ ..Ƿ  → ƿ ..ƿ  Watin-B */
+                {0x0220, 0x0220,  -130}, /*  1x Ƞ ..Ƞ  → ƞ ..ƞ  Watin-B */
+                {0x023b, 0x023b,    +1}, /*  1x Ȼ ..Ȼ  → ȼ ..ȼ  Watin-B */
+                {0x023d, 0x023d,  -163}, /*  1x Ƚ ..Ƚ  → ƚ ..ƚ  Watin-B */
+                {0x0241, 0x0241,    +1}, /*  1x Ɂ ..Ɂ  → ɂ ..ɂ  Watin-B */
+                {0x0243, 0x0243,  -195}, /*  1x Ƀ ..Ƀ  → ƀ ..ƀ  Watin-B */
+                {0x0244, 0x0244,   +69}, /*  1x Ʉ ..Ʉ  → ʉ ..ʉ  Watin-B */
+                {0x0245, 0x0245,   +71}, /*  1x Ʌ ..Ʌ  → ʌ ..ʌ  Watin-B */
+                {0x0246, 0x0246,    +1}, /*  1x Ɇ ..Ɇ  → ɇ ..ɇ  Watin-B */
+                {0x0248, 0x0248,    +1}, /*  1x Ɉ ..Ɉ  → ɉ ..ɉ  Watin-B */
+                {0x024a, 0x024a,    +1}, /*  1x Ɋ ..Ɋ  → ɋ ..ɋ  Watin-B */
+                {0x024c, 0x024c,    +1}, /*  1x Ɍ ..Ɍ  → ɍ ..ɍ  Watin-B */
+                {0x024e, 0x024e,    +1}, /*  1x Ɏ ..Ɏ  → ɏ ..ɏ  Watin-B */
+                {0x0386, 0x0386,   +38}, /*  1x Ά ..Ά  → ά ..ά  Greek */
+                {0x0388, 0x038a,   +37}, /*  3x Έ ..Ί  → έ ..ί  Greek */
+                {0x038c, 0x038c,   +64}, /*  1x Ό ..Ό  → ό ..ό  Greek */
+                {0x038e, 0x038f,   +63}, /*  2x Ύ ..Ώ  → ύ ..ώ  Greek */
+                {0x0391, 0x03a1,   +32}, /* 17x Α ..Ρ  → α ..ρ  Greek */
+                {0x03a3, 0x03ab,   +32}, /*  9x Σ ..Ϋ  → σ ..ϋ  Greek */
+                {0x03dc, 0x03dc,    +1}, /*  1x Ϝ ..Ϝ  → ϝ ..ϝ  Greek */
+                {0x03f4, 0x03f4,   -60}, /*  1x ϴ ..ϴ  → θ ..θ  Greek */
+                {0x0400, 0x040f,   +80}, /* 16x Ѐ ..Џ  → ѐ ..џ  Cyrillic */
+                {0x0410, 0x042f,   +32}, /* 32x А ..Я  → а ..я  Cyrillic */
+                {0x0460, 0x0460,    +1}, /*  1x Ѡ ..Ѡ  → ѡ ..ѡ  Cyrillic */
+                {0x0462, 0x0462,    +1}, /*  1x Ѣ ..Ѣ  → ѣ ..ѣ  Cyrillic */
+                {0x0464, 0x0464,    +1}, /*  1x Ѥ ..Ѥ  → ѥ ..ѥ  Cyrillic */
+                {0x0472, 0x0472,    +1}, /*  1x Ѳ ..Ѳ  → ѳ ..ѳ  Cyrillic */
+                {0x0490, 0x0490,    +1}, /*  1x Ґ ..Ґ  → ґ ..ґ  Cyrillic */
+                {0x0498, 0x0498,    +1}, /*  1x Ҙ ..Ҙ  → ҙ ..ҙ  Cyrillic */
+                {0x049a, 0x049a,    +1}, /*  1x Қ ..Қ  → қ ..қ  Cyrillic */
+                {0x0531, 0x0556,   +48}, /* 38x Ա ..Ֆ  → ա ..ֆ  Armenian */
+                {0x10a0, 0x10c5, +7264}, /* 38x Ⴀ ..Ⴥ  → ⴀ ..ⴥ  Georgian */
+                {0x10c7, 0x10c7, +7264}, /*  1x Ⴧ ..Ⴧ  → ⴧ ..ⴧ  Georgian */
+                {0x10cd, 0x10cd, +7264}, /*  1x Ⴭ ..Ⴭ  → ⴭ ..ⴭ  Georgian */
+                {0x13f0, 0x13f5,    +8}, /*  6x Ᏸ ..Ᏽ  → ᏸ ..ᏽ  Cherokee */
+                {0x1c90, 0x1cba, -3008}, /* 43x Ა ..Ჺ  → ა ..ჺ  Georgian2 */
+                {0x1cbd, 0x1cbf, -3008}, /*  3x Ჽ ..Ჿ  → ჽ ..ჿ  Georgian2 */
+                {0x1f08, 0x1f0f,    -8}, /*  8x Ἀ ..Ἇ  → ἀ ..ἇ  Greek2 */
+                {0x1f18, 0x1f1d,    -8}, /*  6x Ἐ ..Ἕ  → ἐ ..ἕ  Greek2 */
+                {0x1f28, 0x1f2f,    -8}, /*  8x Ἠ ..Ἧ  → ἠ ..ἧ  Greek2 */
+                {0x1f38, 0x1f3f,    -8}, /*  8x Ἰ ..Ἷ  → ἰ ..ἷ  Greek2 */
+                {0x1f48, 0x1f4d,    -8}, /*  6x Ὀ ..Ὅ  → ὀ ..ὅ  Greek2 */
+                {0x1f59, 0x1f59,    -8}, /*  1x Ὑ ..Ὑ  → ὑ ..ὑ  Greek2 */
+                {0x1f5b, 0x1f5b,    -8}, /*  1x Ὓ ..Ὓ  → ὓ ..ὓ  Greek2 */
+                {0x1f5d, 0x1f5d,    -8}, /*  1x Ὕ ..Ὕ  → ὕ ..ὕ  Greek2 */
+                {0x1f5f, 0x1f5f,    -8}, /*  1x Ὗ ..Ὗ  → ὗ ..ὗ  Greek2 */
+                {0x1f68, 0x1f6f,    -8}, /*  8x Ὠ ..Ὧ  → ὠ ..ὧ  Greek2 */
+                {0x1f88, 0x1f8f,    -8}, /*  8x ᾈ ..ᾏ  → ᾀ ..ᾇ  Greek2 */
+                {0x1f98, 0x1f9f,    -8}, /*  8x ᾘ ..ᾟ  → ᾐ ..ᾗ  Greek2 */
+                {0x1fa8, 0x1faf,    -8}, /*  8x ᾨ ..ᾯ  → ᾠ ..ᾧ  Greek2 */
+                {0x1fb8, 0x1fb9,    -8}, /*  2x Ᾰ ..Ᾱ  → ᾰ ..ᾱ  Greek2 */
+                {0x1fba, 0x1fbb,   -74}, /*  2x Ὰ ..Ά  → ὰ ..ά  Greek2 */
+                {0x1fbc, 0x1fbc,    -9}, /*  1x ᾼ ..ᾼ  → ᾳ ..ᾳ  Greek2 */
+                {0x1fc8, 0x1fcb,   -86}, /*  4x Ὲ ..Ή  → ὲ ..ή  Greek2 */
+                {0x1fcc, 0x1fcc,    -9}, /*  1x ῌ ..ῌ  → ῃ ..ῃ  Greek2 */
+                {0x1fd8, 0x1fd9,    -8}, /*  2x Ῐ ..Ῑ  → ῐ ..ῑ  Greek2 */
+                {0x1fda, 0x1fdb,  -100}, /*  2x Ὶ ..Ί  → ὶ ..ί  Greek2 */
+                {0x1fe8, 0x1fe9,    -8}, /*  2x Ῠ ..Ῡ  → ῠ ..ῡ  Greek2 */
+                {0x1fea, 0x1feb,  -112}, /*  2x Ὺ ..Ύ  → ὺ ..ύ  Greek2 */
+                {0x1fec, 0x1fec,    -7}, /*  1x Ῥ ..Ῥ  → ῥ ..ῥ  Greek2 */
+                {0x1ff8, 0x1ff9,  -128}, /*  2x Ὸ ..Ό  → ὸ ..ό  Greek2 */
+                {0x1ffa, 0x1ffb,  -126}, /*  2x Ὼ ..Ώ  → ὼ ..ώ  Greek2 */
+                {0x1ffc, 0x1ffc,    -9}, /*  1x ῼ ..ῼ  → ῳ ..ῳ  Greek2 */
+                {0x2126, 0x2126, -7517}, /*  1x Ω ..Ω  → ω ..ω  Letterlike */
+                {0x212a, 0x212a, -8383}, /*  1x K ..K  → k ..k  Letterlike */
+                {0x212b, 0x212b, -8262}, /*  1x Å ..Å  → å ..å  Letterlike */
+                {0x2132, 0x2132,   +28}, /*  1x Ⅎ ..Ⅎ  → ⅎ ..ⅎ  Letterlike */
+                {0x2160, 0x216f,   +16}, /* 16x Ⅰ ..Ⅿ  → ⅰ ..ⅿ  Numbery */
+                {0x2183, 0x2183,    +1}, /*  1x Ↄ ..Ↄ  → ↄ ..ↄ  Numbery */
+                {0x24b6, 0x24cf,   +26}, /* 26x Ⓐ ..Ⓩ  → ⓐ ..ⓩ  Enclosed */
+                {0x2c00, 0x2c2e,   +48}, /* 47x Ⰰ ..Ⱞ  → ⰰ ..ⱞ  Glagolitic */
+                {0xff21, 0xff3a,   +32}, /* 26x A..Z → a..z Dubs */
+            };
+            l = 0;
+            r = n = sizeof(kLower) / sizeof(kLower[0]);
+            while (l < r) {
+                m = (l + r) >> 1;
+                if (kLower[m].b < c) {
+                    l = m + 1;
+                } else {
+                    r = m;
+                }
+            }
+            if (l < n && kLower[l].a <= c && c <= kLower[l].b) {
+                return c + kLower[l].d;
+            } else {
+                return c;
+            }
+        }
+    } else {
+        static struct {
+            unsigned a;
+            unsigned b;
+            short d;
+        } kAstralLower[] = {
+            {0x10400, 0x10427,   +40}, /* 40x 𐐀 ..𐐧  → 𐐨 ..𐑏  Deseret */
+            {0x104b0, 0x104d3,   +40}, /* 36x 𐒰 ..𐓓  → 𐓘 ..𐓻  Osage */
+            {0x1d400, 0x1d419,   +26}, /* 26x 𝐀 ..𝐙  → 𝐚 ..𝐳  Math */
+            {0x1d43c, 0x1d44d,   +26}, /* 18x 𝐼 ..𝑍  → 𝑖 ..𝑧  Math */
+            {0x1d468, 0x1d481,   +26}, /* 26x 𝑨 ..𝒁  → 𝒂 ..𝒛  Math */
+            {0x1d4ae, 0x1d4b5,   +26}, /*  8x 𝒮 ..𝒵  → 𝓈 ..𝓏  Math */
+            {0x1d4d0, 0x1d4e9,   +26}, /* 26x 𝓐 ..𝓩  → 𝓪 ..𝔃  Math */
+            {0x1d50d, 0x1d514,   +26}, /*  8x 𝔍 ..𝔔  → 𝔧 ..𝔮  Math */
+            {0x1d56c, 0x1d585,   +26}, /* 26x 𝕬 ..𝖅  → 𝖆 ..𝖟  Math */
+            {0x1d5a0, 0x1d5b9,   +26}, /* 26x 𝖠 ..𝖹  → 𝖺 ..𝗓  Math */
+            {0x1d5d4, 0x1d5ed,   +26}, /* 26x 𝗔 ..𝗭  → 𝗮 ..𝘇  Math */
+            {0x1d608, 0x1d621,   +26}, /* 26x 𝘈 ..𝘡  → 𝘢 ..𝘻  Math */
+            {0x1d63c, 0x1d655,  -442}, /* 26x 𝘼 ..𝙕  → 𝒂 ..𝒛  Math */
+            {0x1d670, 0x1d689,   +26}, /* 26x 𝙰 ..𝚉  → 𝚊 ..𝚣  Math */
+            {0x1d6a8, 0x1d6b8,   +26}, /* 17x 𝚨 ..𝚸  → 𝛂 ..𝛒  Math */
+            {0x1d6e2, 0x1d6f2,   +26}, /* 17x 𝛢 ..𝛲  → 𝛼 ..𝜌  Math */
+            {0x1d71c, 0x1d72c,   +26}, /* 17x 𝜜 ..𝜬  → 𝜶 ..𝝆  Math */
+            {0x1d756, 0x1d766,   +26}, /* 17x 𝝖 ..𝝦  → 𝝰 ..𝞀  Math */
+            {0x1d790, 0x1d7a0,   -90}, /* 17x 𝞐 ..𝞠  → 𝜶 ..𝝆  Math */
+        };
+        l = 0;
+        r = n = sizeof(kAstralLower) / sizeof(kAstralLower[0]);
+        while (l < r) {
+            m = (l + r) >> 1;
+            if (kAstralLower[m].b < c) {
+                l = m + 1;
+            } else {
+                r = m;
+            }
+        }
+        if (l < n && kAstralLower[l].a <= c && c <= kAstralLower[l].b) {
+            return c + kAstralLower[l].d;
+        } else {
+            return c;
+        }
+    }
+}
+
+unsigned bestlineUppercase(unsigned c) {
+    int m, l, r, n;
+    if (c < 0200) {
+        if ('a' <= c && c <= 'z') {
+            return c - 32;
+        } else {
+            return c;
+        }
+    } else if (c <= 0xffff) {
+        if ((0x0101 <= c && c <= 0x0177) || /* 60x ā..ŵ → Ā..ā Watin-A */
+            (0x01df <= c && c <= 0x01ef) || /*  9x ǟ..ǯ → Ǟ..Ǯ Watin-B */
+            (0x01f8 <= c && c <= 0x021e) || /* 20x ǹ..ȟ → Ǹ..Ȟ Watin-B */
+            (0x0222 <= c && c <= 0x0232) || /*  9x ȣ..ȳ → Ȣ..Ȳ Watin-B */
+            (0x1e01 <= c && c <= 0x1eff)) { /*256x ḁ..ỿ → Ḁ..Ỿ Watin-C */
+            if (c == 0x0131) return c + 232;
+            if (c == 0x1e9e) return c;
+            return c - (c & 1);
+        } else if (0x01d0 <= c && c <= 0x01dc) {
+            return c - (~c & 1); /* 7x ǐ..ǜ → Ǐ..Ǜ Watin-B */
+        } else if (0xab70 <= c && c <= 0xabbf) {
+            return c - 38864; /* 80x ꭰ ..ꮿ  → Ꭰ ..Ꮿ  Cherokee Supplement */
+        } else {
+            static const struct {
+                unsigned short a;
+                unsigned short b;
+                short d;
+            } kUpper[] = {
+                {0x00b5, 0x00b5,  +743}, /*  1x µ ..µ  → Μ ..Μ  Watin */
+                {0x00e0, 0x00f6,   -32}, /* 23x à ..ö  → À ..Ö  Watin */
+                {0x00f8, 0x00fe,   -32}, /*  7x ø ..þ  → Ø ..Þ  Watin */
+                {0x00ff, 0x00ff,  +121}, /*  1x ÿ ..ÿ  → Ÿ ..Ÿ  Watin */
+                {0x017a, 0x017a,    -1}, /*  1x ź ..ź  → Ź ..Ź  Watin-A */
+                {0x017c, 0x017c,    -1}, /*  1x ż ..ż  → Ż ..Ż  Watin-A */
+                {0x017e, 0x017e,    -1}, /*  1x ž ..ž  → Ž ..Ž  Watin-A */
+                {0x017f, 0x017f,  -300}, /*  1x ſ ..ſ  → S ..S  Watin-A */
+                {0x0180, 0x0180,  +195}, /*  1x ƀ ..ƀ  → Ƀ ..Ƀ  Watin-B */
+                {0x0183, 0x0183,    -1}, /*  1x ƃ ..ƃ  → Ƃ ..Ƃ  Watin-B */
+                {0x0185, 0x0185,    -1}, /*  1x ƅ ..ƅ  → Ƅ ..Ƅ  Watin-B */
+                {0x0188, 0x0188,    -1}, /*  1x ƈ ..ƈ  → Ƈ ..Ƈ  Watin-B */
+                {0x018c, 0x018c,    -1}, /*  1x ƌ ..ƌ  → Ƌ ..Ƌ  Watin-B */
+                {0x0192, 0x0192,    -1}, /*  1x ƒ ..ƒ  → Ƒ ..Ƒ  Watin-B */
+                {0x0195, 0x0195,   +97}, /*  1x ƕ ..ƕ  → Ƕ ..Ƕ  Watin-B */
+                {0x0199, 0x0199,    -1}, /*  1x ƙ ..ƙ  → Ƙ ..Ƙ  Watin-B */
+                {0x019a, 0x019a,  +163}, /*  1x ƚ ..ƚ  → Ƚ ..Ƚ  Watin-B */
+                {0x019e, 0x019e,  +130}, /*  1x ƞ ..ƞ  → Ƞ ..Ƞ  Watin-B */
+                {0x01a1, 0x01a1,    -1}, /*  1x ơ ..ơ  → Ơ ..Ơ  Watin-B */
+                {0x01a3, 0x01a3,    -1}, /*  1x ƣ ..ƣ  → Ƣ ..Ƣ  Watin-B */
+                {0x01a5, 0x01a5,    -1}, /*  1x ƥ ..ƥ  → Ƥ ..Ƥ  Watin-B */
+                {0x01a8, 0x01a8,    -1}, /*  1x ƨ ..ƨ  → Ƨ ..Ƨ  Watin-B */
+                {0x01ad, 0x01ad,    -1}, /*  1x ƭ ..ƭ  → Ƭ ..Ƭ  Watin-B */
+                {0x01b0, 0x01b0,    -1}, /*  1x ư ..ư  → Ư ..Ư  Watin-B */
+                {0x01b4, 0x01b4,    -1}, /*  1x ƴ ..ƴ  → Ƴ ..Ƴ  Watin-B */
+                {0x01b6, 0x01b6,    -1}, /*  1x ƶ ..ƶ  → Ƶ ..Ƶ  Watin-B */
+                {0x01b9, 0x01b9,    -1}, /*  1x ƹ ..ƹ  → Ƹ ..Ƹ  Watin-B */
+                {0x01bd, 0x01bd,    -1}, /*  1x ƽ ..ƽ  → Ƽ ..Ƽ  Watin-B */
+                {0x01bf, 0x01bf,   +56}, /*  1x ƿ ..ƿ  → Ƿ ..Ƿ  Watin-B */
+                {0x01c5, 0x01c5,    -1}, /*  1x Dž ..Dž  → DŽ ..DŽ  Watin-B */
+                {0x01c6, 0x01c6,    -2}, /*  1x dž ..dž  → DŽ ..DŽ  Watin-B */
+                {0x01c8, 0x01c8,    -1}, /*  1x Lj ..Lj  → LJ ..LJ  Watin-B */
+                {0x01c9, 0x01c9,    -2}, /*  1x lj ..lj  → LJ ..LJ  Watin-B */
+                {0x01cb, 0x01cb,    -1}, /*  1x Nj ..Nj  → NJ ..NJ  Watin-B */
+                {0x01cc, 0x01cc,    -2}, /*  1x nj ..nj  → NJ ..NJ  Watin-B */
+                {0x01ce, 0x01ce,    -1}, /*  1x ǎ ..ǎ  → Ǎ ..Ǎ  Watin-B */
+                {0x01dd, 0x01dd,   -79}, /*  1x ǝ ..ǝ  → Ǝ ..Ǝ  Watin-B */
+                {0x01f2, 0x01f2,    -1}, /*  1x Dz ..Dz  → DZ ..DZ  Watin-B */
+                {0x01f3, 0x01f3,    -2}, /*  1x dz ..dz  → DZ ..DZ  Watin-B */
+                {0x01f5, 0x01f5,    -1}, /*  1x ǵ ..ǵ  → Ǵ ..Ǵ  Watin-B */
+                {0x023c, 0x023c,    -1}, /*  1x ȼ ..ȼ  → Ȼ ..Ȼ  Watin-B */
+                {0x023f, 0x0240,+10815}, /*  2x ȿ ..ɀ  → Ȿ ..Ɀ  Watin-B */
+                {0x0242, 0x0242,    -1}, /*  1x ɂ ..ɂ  → Ɂ ..Ɂ  Watin-B */
+                {0x0247, 0x0247,    -1}, /*  1x ɇ ..ɇ  → Ɇ ..Ɇ  Watin-B */
+                {0x0249, 0x0249,    -1}, /*  1x ɉ ..ɉ  → Ɉ ..Ɉ  Watin-B */
+                {0x024b, 0x024b,    -1}, /*  1x ɋ ..ɋ  → Ɋ ..Ɋ  Watin-B */
+                {0x024d, 0x024d,    -1}, /*  1x ɍ ..ɍ  → Ɍ ..Ɍ  Watin-B */
+                {0x024f, 0x024f,    -1}, /*  1x ɏ ..ɏ  → Ɏ ..Ɏ  Watin-B */
+                {0x037b, 0x037d,  +130}, /*  3x ͻ ..ͽ  → Ͻ ..Ͽ  Greek */
+                {0x03ac, 0x03ac,   -38}, /*  1x ά ..ά  → Ά ..Ά  Greek */
+                {0x03ad, 0x03af,   -37}, /*  3x έ ..ί  → Έ ..Ί  Greek */
+                {0x03b1, 0x03c1,   -32}, /* 17x α ..ρ  → Α ..Ρ  Greek */
+                {0x03c2, 0x03c2,   -31}, /*  1x ς ..ς  → Σ ..Σ  Greek */
+                {0x03c3, 0x03cb,   -32}, /*  9x σ ..ϋ  → Σ ..Ϋ  Greek */
+                {0x03cc, 0x03cc,   -64}, /*  1x ό ..ό  → Ό ..Ό  Greek */
+                {0x03cd, 0x03ce,   -63}, /*  2x ύ ..ώ  → Ύ ..Ώ  Greek */
+                {0x03d0, 0x03d0,   -62}, /*  1x ϐ ..ϐ  → Β ..Β  Greek */
+                {0x03d1, 0x03d1,   -57}, /*  1x ϑ ..ϑ  → Θ ..Θ  Greek */
+                {0x03d5, 0x03d5,   -47}, /*  1x ϕ ..ϕ  → Φ ..Φ  Greek */
+                {0x03d6, 0x03d6,   -54}, /*  1x ϖ ..ϖ  → Π ..Π  Greek */
+                {0x03dd, 0x03dd,    -1}, /*  1x ϝ ..ϝ  → Ϝ ..Ϝ  Greek */
+                {0x03f0, 0x03f0,   -86}, /*  1x ϰ ..ϰ  → Κ ..Κ  Greek */
+                {0x03f1, 0x03f1,   -80}, /*  1x ϱ ..ϱ  → Ρ ..Ρ  Greek */
+                {0x03f5, 0x03f5,   -96}, /*  1x ϵ ..ϵ  → Ε ..Ε  Greek */
+                {0x0430, 0x044f,   -32}, /* 32x а ..я  → А ..Я  Cyrillic */
+                {0x0450, 0x045f,   -80}, /* 16x ѐ ..џ  → Ѐ ..Џ  Cyrillic */
+                {0x0461, 0x0461,    -1}, /*  1x ѡ ..ѡ  → Ѡ ..Ѡ  Cyrillic */
+                {0x0463, 0x0463,    -1}, /*  1x ѣ ..ѣ  → Ѣ ..Ѣ  Cyrillic */
+                {0x0465, 0x0465,    -1}, /*  1x ѥ ..ѥ  → Ѥ ..Ѥ  Cyrillic */
+                {0x0473, 0x0473,    -1}, /*  1x ѳ ..ѳ  → Ѳ ..Ѳ  Cyrillic */
+                {0x0491, 0x0491,    -1}, /*  1x ґ ..ґ  → Ґ ..Ґ  Cyrillic */
+                {0x0499, 0x0499,    -1}, /*  1x ҙ ..ҙ  → Ҙ ..Ҙ  Cyrillic */
+                {0x049b, 0x049b,    -1}, /*  1x қ ..қ  → Қ ..Қ  Cyrillic */
+                {0x0561, 0x0586,   -48}, /* 38x ա ..ֆ  → Ա ..Ֆ  Armenian */
+                {0x10d0, 0x10fa, +3008}, /* 43x ა ..ჺ  → Ა ..Ჺ  Georgian */
+                {0x10fd, 0x10ff, +3008}, /*  3x ჽ ..ჿ  → Ჽ ..Ჿ  Georgian */
+                {0x13f8, 0x13fd,    -8}, /*  6x ᏸ ..ᏽ  → Ᏸ ..Ᏽ  Cherokee */
+                {0x214e, 0x214e,   -28}, /*  1x ⅎ ..ⅎ  → Ⅎ ..Ⅎ  Letterlike */
+                {0x2170, 0x217f,   -16}, /* 16x ⅰ ..ⅿ  → Ⅰ ..Ⅿ  Numbery */
+                {0x2184, 0x2184,    -1}, /*  1x ↄ ..ↄ  → Ↄ ..Ↄ  Numbery */
+                {0x24d0, 0x24e9,   -26}, /* 26x ⓐ ..ⓩ  → Ⓐ ..Ⓩ  Enclosed */
+                {0x2c30, 0x2c5e,   -48}, /* 47x ⰰ ..ⱞ  → Ⰰ ..Ⱞ  Glagolitic */
+                {0x2d00, 0x2d25, -7264}, /* 38x ⴀ ..ⴥ  → Ⴀ ..Ⴥ  Georgian2 */
+                {0x2d27, 0x2d27, -7264}, /*  1x ⴧ ..ⴧ  → Ⴧ ..Ⴧ  Georgian2 */
+                {0x2d2d, 0x2d2d, -7264}, /*  1x ⴭ ..ⴭ  → Ⴭ ..Ⴭ  Georgian2 */
+                {0xff41, 0xff5a,   -32}, /* 26x a..z → A..Z Dubs */
+            };
+            l = 0;
+            r = n = sizeof(kUpper) / sizeof(kUpper[0]);
+            while (l < r) {
+                m = (l + r) >> 1;
+                if (kUpper[m].b < c) {
+                    l = m + 1;
+                } else {
+                    r = m;
+                }
+            }
+            if (l < n && kUpper[l].a <= c && c <= kUpper[l].b) {
+                return c + kUpper[l].d;
+            } else {
+                return c;
+            }
+        }
+    } else {
+        static const struct {
+            unsigned a;
+            unsigned b;
+            short d;
+        } kAstralUpper[] = {
+            {0x10428, 0x1044f,   -40}, /* 40x 𐐨..𐑏 → 𐐀..𐐧 Deseret */
+            {0x104d8, 0x104fb,   -40}, /* 36x 𐓘..𐓻 → 𐒰..𐓓 Osage */
+            {0x1d41a, 0x1d433,   -26}, /* 26x 𝐚..𝐳 → 𝐀..𝐙 Math */
+            {0x1d456, 0x1d467,   -26}, /* 18x 𝑖..𝑧 → 𝐼..𝑍 Math */
+            {0x1d482, 0x1d49b,   -26}, /* 26x 𝒂..𝒛 → 𝑨..𝒁 Math */
+            {0x1d4c8, 0x1d4cf,   -26}, /*  8x 𝓈..𝓏 → 𝒮..𝒵 Math */
+            {0x1d4ea, 0x1d503,   -26}, /* 26x 𝓪..𝔃 → 𝓐..𝓩 Math */
+            {0x1d527, 0x1d52e,   -26}, /*  8x 𝔧..𝔮 → 𝔍..𝔔 Math */
+            {0x1d586, 0x1d59f,   -26}, /* 26x 𝖆..𝖟 → 𝕬..𝖅 Math */
+            {0x1d5ba, 0x1d5d3,   -26}, /* 26x 𝖺..𝗓 → 𝖠..𝖹 Math */
+            {0x1d5ee, 0x1d607,   -26}, /* 26x 𝗮..𝘇 → 𝗔..𝗭 Math */
+            {0x1d622, 0x1d63b,   -26}, /* 26x 𝘢..𝘻 → 𝘈..𝘡 Math */
+            {0x1d68a, 0x1d6a3,  +442}, /* 26x 𝒂..𝒛 → 𝘼..𝙕 Math */
+            {0x1d6c2, 0x1d6d2,   -26}, /* 26x 𝚊..𝚣 → 𝙰..𝚉 Math */
+            {0x1d6fc, 0x1d70c,   -26}, /* 17x 𝛂..𝛒 → 𝚨..𝚸 Math */
+            {0x1d736, 0x1d746,   -26}, /* 17x 𝛼..𝜌 → 𝛢..𝛲 Math */
+            {0x1d770, 0x1d780,   -26}, /* 17x 𝜶..𝝆 → 𝜜..𝜬 Math */
+            {0x1d770, 0x1d756,   -26}, /* 17x 𝝰..𝞀 → 𝝖..𝝦 Math */
+            {0x1d736, 0x1d790,   -90}, /* 17x 𝜶..𝝆 → 𝞐..𝞠 Math */
+        };
+        l = 0;
+        r = n = sizeof(kAstralUpper) / sizeof(kAstralUpper[0]);
+        while (l < r) {
+            m = (l + r) >> 1;
+            if (kAstralUpper[m].b < c) {
+                l = m + 1;
+            } else {
+                r = m;
+            }
+        }
+        if (l < n && kAstralUpper[l].a <= c && c <= kAstralUpper[l].b) {
+            return c + kAstralUpper[l].d;
+        } else {
+            return c;
+        }
+    }
+}
+
+static char NotSeparator(unsigned c) {
+    return !IsSeparator(c);
+}
+
+static unsigned GetMirror(const unsigned short A[][2], size_t n, unsigned c) {
+    int l, m, r;
+    l = 0;
+    r = n - 1;
+    while (l <= r) {
+        m = (l + r) >> 1;
+        if (A[m][0] < c) {
+            l = m + 1;
+        } else if (A[m][0] > c) {
+            r = m - 1;
+        } else {
+            return A[m][1];
+        }
+    }
+    return 0;
+}
+
+static unsigned GetMirrorLeft(unsigned c) {
+    static const unsigned short kMirrorRight[][2] = {
+        {L')', L'('},   {L']', L'['},   {L'}', L'{'},   {L'⁆', L'⁅'},
+        {L'⁾', L'⁽'},   {L'₎', L'₍'},   {L'⌉', L'⌈'},   {L'⌋', L'⌊'},
+        {L'〉', L'〈'}, {L'❩', L'❨'},   {L'❫', L'❪'},   {L'❭', L'❬'},
+        {L'❯', L'❮'},   {L'❱', L'❰'},   {L'❳', L'❲'},   {L'❵', L'❴'},
+        {L'⟆', L'⟅'},   {L'⟧', L'⟦'},   {L'⟩', L'⟨'},   {L'⟫', L'⟪'},
+        {L'⟭', L'⟬'},   {L'⟯', L'⟮'},   {L'⦄', L'⦃'},   {L'⦆', L'⦅'},
+        {L'⦈', L'⦇'},   {L'⦊', L'⦉'},   {L'⦌', L'⦋'},   {L'⦎', L'⦏'},
+        {L'⦐', L'⦍'},   {L'⦒', L'⦑'},   {L'⦔', L'⦓'},   {L'⦘', L'⦗'},
+        {L'⧙', L'⧘'},   {L'⧛', L'⧚'},   {L'⧽', L'⧼'},   {L'﹚', L'﹙'},
+        {L'﹜', L'﹛'}, {L'﹞', L'﹝'}, {L')', L'('}, {L']', L'['},
+        {L'}', L'{'}, {L'」', L'「'},
+    };
+    return GetMirror(kMirrorRight,
+                     sizeof(kMirrorRight) / sizeof(kMirrorRight[0]),
+                     c);
+}
+
+static unsigned GetMirrorRight(unsigned c) {
+    static const unsigned short kMirrorLeft[][2] = {
+        {L'(', L')'},   {L'[', L']'},   {L'{', L'}'},   {L'⁅', L'⁆'},
+        {L'⁽', L'⁾'},   {L'₍', L'₎'},   {L'⌈', L'⌉'},   {L'⌊', L'⌋'},
+        {L'〈', L'〉'}, {L'❨', L'❩'},   {L'❪', L'❫'},   {L'❬', L'❭'},
+        {L'❮', L'❯'},   {L'❰', L'❱'},   {L'❲', L'❳'},   {L'❴', L'❵'},
+        {L'⟅', L'⟆'},   {L'⟦', L'⟧'},   {L'⟨', L'⟩'},   {L'⟪', L'⟫'},
+        {L'⟬', L'⟭'},   {L'⟮', L'⟯'},   {L'⦃', L'⦄'},   {L'⦅', L'⦆'},
+        {L'⦇', L'⦈'},   {L'⦉', L'⦊'},   {L'⦋', L'⦌'},   {L'⦍', L'⦐'},
+        {L'⦏', L'⦎'},   {L'⦑', L'⦒'},   {L'⦓', L'⦔'},   {L'⦗', L'⦘'},
+        {L'⧘', L'⧙'},   {L'⧚', L'⧛'},   {L'⧼', L'⧽'},   {L'﹙', L'﹚'},
+        {L'﹛', L'﹜'}, {L'﹝', L'﹞'}, {L'(', L')'}, {L'[', L']'},
+        {L'{', L'}'}, {L'「', L'」'},
+    };
+    return GetMirror(kMirrorLeft,
+                     sizeof(kMirrorLeft) / sizeof(kMirrorLeft[0]),
+                     c);
+}
+
+static char IsXeparator(unsigned c) {
+    return IsSeparator(c) && !GetMirrorLeft(c) && !GetMirrorRight(c);
+}
+
+static unsigned Capitalize(unsigned c) {
+    if (!iscapital) {
+        c = bestlineUppercase(c);
+        iscapital = 1;
+    }
+    return c;
+}
+
+static inline int Bsr(unsigned long long x) {
+#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
+    int b;
+    b = __builtin_clzll(x);
+    b ^= sizeof(unsigned long long) * CHAR_BIT - 1;
+    return b;
+#else
+    static const char kDebruijn[64] = {
+        0,  47, 1,  56, 48, 27, 2,  60, 57, 49, 41, 37, 28, 16, 3,  61,
+        54, 58, 35, 52, 50, 42, 21, 44, 38, 32, 29, 23, 17, 11, 4,  62,
+        46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, 31, 22, 10, 45,
+        25, 39, 14, 33, 19, 30, 9,  24, 13, 18, 8,  12, 7,  6,  5,  63,
+    };
+    x |= x >> 1;
+    x |= x >> 2;
+    x |= x >> 4;
+    x |= x >> 8;
+    x |= x >> 16;
+    x |= x >> 32;
+    return kDebruijn[(x * 0x03f79d71b4cb0a89) >> 58];
+#endif
+}
+
+static struct rune DecodeUtf8(int c) {
+    struct rune r;
+    if (c < 252) {
+        r.n = Bsr(255 & ~c);
+        r.c = c & (((1 << r.n) - 1) | 3);
+        r.n = 6 - r.n;
+    } else {
+        r.c = c & 3;
+        r.n = 5;
+    }
+    return r;
+}
+
+static unsigned long long EncodeUtf8(unsigned c) {
+    static const unsigned short kTpEnc[32 - 7] = {
+        1|0300<<8, 1|0300<<8, 1|0300<<8, 1|0300<<8, 2|0340<<8,
+        2|0340<<8, 2|0340<<8, 2|0340<<8, 2|0340<<8, 3|0360<<8,
+        3|0360<<8, 3|0360<<8, 3|0360<<8, 3|0360<<8, 4|0370<<8,
+        4|0370<<8, 4|0370<<8, 4|0370<<8, 4|0370<<8, 5|0374<<8,
+        5|0374<<8, 5|0374<<8, 5|0374<<8, 5|0374<<8, 5|0374<<8,
+    };
+    int e, n;
+    unsigned long long w;
+    if (c < 0200) return c;
+    e = kTpEnc[Bsr(c) - 7];
+    n = e & 0xff;
+    w = 0;
+    do {
+        w |= 0200 | (c & 077);
+        w <<= 8;
+        c >>= 6;
+    } while (--n);
+    return c | w | e >> 8;
+}
+
+static struct rune GetUtf8(const char *p, size_t n) {
+    struct rune r;
+    if ((r.n = r.c = 0) < n && (r.c = p[r.n++] & 255) >= 0300) {
+        r.c = DecodeUtf8(r.c).c;
+        while (r.n < n && (p[r.n] & 0300) == 0200) {
+            r.c = r.c << 6 | (p[r.n++] & 077);
+        }
+    }
+    return r;
+}
+
+static char *FormatUnsigned(char *p, unsigned x) {
+    char t;
+    size_t i, a, b;
+    i = 0;
+    do {
+        p[i++] = x % 10 + '0';
+        x = x / 10;
+    } while (x > 0);
+    p[i] = '\0';
+    if (i) {
+        for (a = 0, b = i - 1; a < b; ++a, --b) {
+            t = p[a];
+            p[a] = p[b];
+            p[b] = t;
+        }
+    }
+    return p + i;
+}
+
+static void abInit(struct abuf *a) {
+    a->len = 0;
+    a->cap = 16;
+    a->b = (char *)malloc(a->cap);
+    a->b[0] = 0;
+}
+
+static char abGrow(struct abuf *a, int need) {
+    int cap;
+    char *b;
+    cap = a->cap;
+    do cap += cap / 2;
+    while (cap < need);
+    if (!(b = (char *)realloc(a->b, cap * sizeof(*a->b)))) return 0;
+    a->cap = cap;
+    a->b = b;
+    return 1;
+}
+
+static void abAppendw(struct abuf *a, unsigned long long w) {
+    char *p;
+    if (a->len + 8 > a->cap && !abGrow(a, a->len + 8)) return;
+    p = a->b + a->len;
+    p[0] = (0x00000000000000FF & w) >> 000;
+    p[1] = (0x000000000000FF00 & w) >> 010;
+    p[2] = (0x0000000000FF0000 & w) >> 020;
+    p[3] = (0x00000000FF000000 & w) >> 030;
+    p[4] = (0x000000FF00000000 & w) >> 040;
+    p[5] = (0x0000FF0000000000 & w) >> 050;
+    p[6] = (0x00FF000000000000 & w) >> 060;
+    p[7] = (0xFF00000000000000 & w) >> 070;
+    a->len += w ? (Bsr(w) >> 3) + 1 : 1;
+}
+
+static void abAppend(struct abuf *a, const char *s, int len) {
+    if (a->len + len + 1 > a->cap && !abGrow(a, a->len + len + 1)) return;
+    memcpy(a->b + a->len, s, len);
+    a->b[a->len + len] = 0;
+    a->len += len;
+}
+
+static void abAppends(struct abuf *a, const char *s) {
+    abAppend(a, s, strlen(s));
+}
+
+static void abAppendu(struct abuf *a, unsigned u) {
+    char b[11];
+    abAppend(a, b, FormatUnsigned(b, u) - b);
+}
+
+static void abFree(struct abuf *a) {
+    free(a->b);
+    a->b = 0;
+}
+
+static size_t GetFdSize(int fd) {
+    struct stat st;
+    st.st_size = 0;
+    fstat(fd, &st);
+    return st.st_size;
+}
+
+static char IsCharDev(int fd) {
+    struct stat st;
+    st.st_mode = 0;
+    fstat(fd, &st);
+    return (st.st_mode & S_IFMT) == S_IFCHR;
+}
+
+static int WaitUntilReady(int fd, int events) {
+    struct pollfd p[1];
+    p[0].fd = fd;
+    p[0].events = events;
+    return poll(p, 1, -1);
+}
+
+static char HasPendingInput(int fd) {
+    struct pollfd p[1];
+    p[0].fd = fd;
+    p[0].events = POLLIN;
+    return poll(p, 1, 0) == 1;
+}
+
+static char *GetLineBlock(FILE *f) {
+    ssize_t rc;
+    char *p = 0;
+    size_t n, c = 0;
+    if ((rc = getdelim(&p, &c, '\n', f)) != EOF) {
+        for (n = rc; n; --n) {
+            if (p[n - 1] == '\r' || p[n - 1] == '\n') {
+                p[n - 1] = 0;
+            } else {
+                break;
+            }
+        }
+        return p;
+    } else {
+        free(p);
+        return 0;
+    }
+}
+
+static ssize_t ReadCharacter(int fd, char *p, size_t n) {
+    int e;
+    size_t i;
+    ssize_t rc;
+    struct rune r;
+    unsigned char c;
+    enum { kAscii, kUtf8, kEsc, kCsi1, kCsi2, kSs, kNf, kStr, kStr2, kDone } t;
+    i = 0;
+    r.c = 0;
+    r.n = 0;
+    e = errno;
+    t = kAscii;
+    if (n) p[0] = 0;
+    do {
+        for (;;) {
+            if (gotint) {
+                errno = EINTR;
+                return -1;
+            }
+            if (n) {
+                rc = read(fd,&c,1);
+            } else {
+                rc = read(fd,0,0);
+            }
+            if (rc == -1 && errno == EINTR) {
+                if (!i) {
+                    return -1;
+                }
+            } else if (rc == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+                if (WaitUntilReady(fd, POLLIN) == -1) {
+                    if (rc == -1 && errno == EINTR) {
+                        if (!i) {
+                            return -1;
+                        }
+                    } else {
+                        return -1;
+                    }
+                }
+            } else if (rc == -1) {
+                return -1;
+            } else if (!rc) {
+                if (!i) {
+                    errno = e;
+                    return 0;
+                } else {
+                    errno = EILSEQ;
+                    return -1;
+                }
+            } else {
+                break;
+            }
+        }
+        if (i + 1 < n) {
+            p[i] = c;
+            p[i+1] = 0;
+        } else if (i < n) {
+            p[i] = 0;
+        }
+        ++i;
+        switch (t) {
+        Whoopsie:
+            if (n) p[0] = c;
+            t = kAscii;
+            i = 1;
+            /* fallthrough */
+        case kAscii:
+            if (c < 0200) {
+                if (c == 033) {
+                    t = kEsc;
+                } else {
+                    t = kDone;
+                }
+            } else if (c >= 0300) {
+                t = kUtf8;
+                r = DecodeUtf8(c);
+            } else {
+                /* ignore overlong sequences */
+            }
+            break;
+        case kUtf8:
+            if ((c & 0300) == 0200) {
+                r.c <<= 6;
+                r.c |= c & 077;
+                if (!--r.n) {
+                    switch (r.c) {
+                    case 033:
+                        t = kEsc; /* parsed but not canonicalized */
+                        break;
+                    case 0x9b:
+                        t = kCsi1; /* unusual but legal */
+                        break;
+                    case 0x8e: /* SS2 (Single Shift Two) */
+                    case 0x8f: /* SS3 (Single Shift Three) */
+                        t = kSs;
+                        break;
+                    case 0x90: /* DCS (Device Control String) */
+                    case 0x98: /* SOS (Start of String) */
+                    case 0x9d: /* OSC (Operating System Command) */
+                    case 0x9e: /* PM  (Privacy Message) */
+                    case 0x9f: /* APC (Application Program Command) */
+                        t = kStr;
+                        break;
+                    default:
+                        t = kDone;
+                        break;
+                    }
+                }
+            } else {
+                goto Whoopsie; /* ignore underlong sequences if not eof */
+            }
+            break;
+        case kEsc:
+            if (0x20 <= c && c <= 0x2f) { /* Nf */
+                t = kNf;
+            } else if (0x30 <= c && c <= 0x3f) { /* Fp */
+                t = kDone;
+            } else if (0x20 <= c && c <= 0x5F) { /* Fe */
+                switch (c) {
+                case '[':
+                    t = kCsi1;
+                    break;
+                case 'N': /* SS2 (Single Shift Two) */
+                case 'O': /* SS3 (Single Shift Three) */
+                    t = kSs;
+                    break;
+                case 'P': /* DCS (Device Control String) */
+                case 'X': /* SOS (Start of String) */
+                case ']': /* OSC (Operating System Command) */
+                case '^': /* PM  (Privacy Message) */
+                case '_': /* APC (Application Program Command) */
+                    t = kStr;
+                    break;
+                case '\\':
+                    goto Whoopsie;
+                default:
+                    t = kDone;
+                    break;
+                }
+            } else if (0x60 <= c && c <= 0x7e) { /* Fs */
+                t = kDone;
+            } else if (c == 033) {
+                if (i < 3) {
+                    /* alt chording */
+                } else {
+                    t = kDone; /* esc mashing */
+                    i = 1;
+                }
+            } else {
+                t = kDone;
+            }
+            break;
+        case kSs:
+            t = kDone;
+            break;
+         case kNf:
+             if (0x30 <= c && c <= 0x7e) {
+                 t = kDone;
+             } else if (!(0x20 <= c && c <= 0x2f)) {
+                 goto Whoopsie;
+             }
+            break;
+        case kCsi1:
+            if (0x20 <= c && c <= 0x2f) {
+                t = kCsi2;
+            } else if (c == '[' && ((i == 3) ||
+                                    (i == 4 && p[1] == 033))) {
+                /* linux function keys */
+            } else if (0x40 <= c && c <= 0x7e) {
+                t = kDone;
+            } else if (!(0x30 <= c && c <= 0x3f)) {
+                goto Whoopsie;
+            }
+            break;
+        case kCsi2:
+            if (0x40 <= c && c <= 0x7e) {
+                t = kDone;
+            } else if (!(0x20 <= c && c <= 0x2f)) {
+                goto Whoopsie;
+            }
+            break;
+        case kStr:
+            switch (c) {
+            case '\a':
+                t = kDone;
+                break;
+            case 0033: /* ESC */
+            case 0302: /* C1 (UTF-8) */
+                t = kStr2;
+                break;
+            default:
+                break;
+            }
+            break;
+        case kStr2:
+            switch (c) {
+            case '\a':
+            case '\\': /* ST (ASCII) */
+            case 0234: /* ST (UTF-8) */
+                t = kDone;
+                break;
+            default:
+                t = kStr;
+                break;
+            }
+            break;
+        default:
+            assert(0);
+        }
+    } while (t != kDone);
+    errno = e;
+    return i;
+}
+
+static char *GetLineChar(int fin, int fout) {
+    size_t got;
+    ssize_t rc;
+    char seq[16];
+    struct abuf a;
+    struct sigaction sa[3];
+    abInit(&a);
+    gotint = 0;
+    sigemptyset(&sa->sa_mask);
+    sa->sa_flags = 0;
+    sa->sa_handler = bestlineOnInt;
+    sigaction(SIGINT,sa,sa+1);
+    sigaction(SIGQUIT,sa,sa+2);
+    for (;;) {
+        if (gotint) {
+            rc = -1;
+            break;
+        }
+        if ((rc = ReadCharacter(fin, seq, sizeof(seq))) == -1) {
+            if (errno == EAGAIN || errno == EWOULDBLOCK) {
+                if (WaitUntilReady(fin, POLLIN) > 0) {
+                    continue;
+                }
+            }
+            if (errno == EINTR) {
+                continue;
+            } else {
+                break;
+            }
+        }
+        if (!(got = rc)) {
+            if (a.len) {
+                break;
+            } else {
+                rc = -1;
+                break;
+            }
+        }
+        if (seq[0] == '\r') {
+            if (HasPendingInput(fin)) {
+                if ((rc = ReadCharacter(fin, seq + 1, sizeof(seq) - 1)) > 0) {
+                    if (seq[0] == '\n') {
+                        break;
+                    }
+                } else {
+                    rc = -1;
+                    break;
+                }
+            } else {
+                write(fout, "\n", 1);
+                break;
+            }
+        } else if (seq[0] == Ctrl('D')) {
+            break;
+        } else if (seq[0] == '\n') {
+            break;
+        } else if (seq[0] == '\b') {
+            while (a.len && (a.b[a.len - 1] & 0300) == 0200) --a.len;
+            if (a.len) --a.len;
+        }
+        if (!IsControl(seq[0])) {
+            abAppend(&a, seq, got);
+        }
+    }
+    sigaction(SIGQUIT,sa+2,0);
+    sigaction(SIGINT,sa+1,0);
+    if (gotint) {
+        abFree(&a);
+        raise(gotint);
+        errno = EINTR;
+        rc = -1;
+    }
+    if (rc != -1) {
+        return a.b;
+    } else {
+        abFree(&a);
+        return 0;
+    }
+}
+
+static char *GetLine(FILE *in, FILE *out) {
+    if (!IsCharDev(fileno(in))) {
+        return GetLineBlock(in);
+    } else {
+        return GetLineChar(fileno(in), fileno(out));
+    }
+}
+
+static char *Copy(char *d, const char *s, size_t n) {
+    memcpy(d, s, n);
+    return d + n;
+}
+
+static int CompareStrings(const char *a, const char *b) {
+    size_t i;
+    int x, y, c;
+    for (i = 0;; ++i) {
+        x = bestlineLowercase(a[i] & 255);
+        y = bestlineLowercase(b[i] & 255);
+        if ((c = x - y) || !x) {
+            return c;
+        }
+    }
+}
+
+static const char *FindSubstringReverse(const char *p, size_t n,
+                                        const char *q, size_t m) {
+    size_t i;
+    if (m <= n) {
+        n -= m;
+        do {
+            for (i = 0; i < m; ++i) {
+                if (p[n + i] != q[i]) {
+                    break;
+                }
+            }
+            if (i == m) {
+                return p + n;
+            }
+        } while (n--);
+    }
+    return 0;
+}
+
+static int ParseUnsigned(const char *s, void *e) {
+    int c, x;
+    for (x = 0; (c = *s++);) {
+        if ('0' <= c && c <= '9') {
+            x = Min(c - '0' + x * 10, 32767);
+        } else {
+            break;
+        }
+    }
+    if (e) *(const char **)e = s;
+    return x;
+}
+
+static size_t GetMonospaceWidth(const char *p, size_t n, char *out_haswides) {
+    int c, d;
+    size_t i, w;
+    struct rune r;
+    char haswides;
+    enum { kAscii, kUtf8, kEsc, kCsi1, kCsi2, kSs, kNf, kStr, kStr2 } t;
+    for (haswides = r.c = r.n = w = i = 0, t = kAscii; i < n; ++i) {
+        c = p[i] & 255;
+        switch (t) {
+        Whoopsie:
+            t = kAscii;
+            /* fallthrough */
+        case kAscii:
+            if (c < 0200) {
+                if (c == 033) {
+                    t = kEsc;
+                } else {
+                    ++w;
+                }
+            } else if (c >= 0300) {
+                t = kUtf8;
+                r = DecodeUtf8(c);
+            }
+            break;
+        case kUtf8:
+            if ((c & 0300) == 0200) {
+                r.c <<= 6;
+                r.c |= c & 077;
+                if (!--r.n) {
+                    switch (r.c) {
+                    case 033:
+                        t = kEsc;
+                        break;
+                    case 0x9b:
+                        t = kCsi1;
+                        break;
+                    case 0x8e:
+                    case 0x8f:
+                        t = kSs;
+                        break;
+                    case 0x90:
+                    case 0x98:
+                    case 0x9d:
+                    case 0x9e:
+                    case 0x9f:
+                        t = kStr;
+                        break;
+                    default:
+                        d = GetMonospaceCharacterWidth(r.c);
+                        d = Max(0, d);
+                        w += d;
+                        haswides |= d > 1;
+                        t = kAscii;
+                        break;
+                    }
+                }
+            } else {
+                goto Whoopsie;
+            }
+            break;
+        case kEsc:
+            if (0x20 <= c && c <= 0x2f) {
+                t = kNf;
+            } else if (0x30 <= c && c <= 0x3f) {
+                t = kAscii;
+            } else if (0x20 <= c && c <= 0x5F) {
+                switch (c) {
+                case '[':
+                    t = kCsi1;
+                    break;
+                case 'N':
+                case 'O':
+                    t = kSs;
+                    break;
+                case 'P':
+                case 'X':
+                case ']':
+                case '^':
+                case '_':
+                    t = kStr;
+                    break;
+                case '\\':
+                    goto Whoopsie;
+                default:
+                    t = kAscii;
+                    break;
+                }
+            } else if (0x60 <= c && c <= 0x7e) {
+                t = kAscii;
+            } else if (c == 033) {
+                if (i == 3) t = kAscii;
+            } else {
+                t = kAscii;
+            }
+            break;
+        case kSs:
+            t = kAscii;
+            break;
+         case kNf:
+             if (0x30 <= c && c <= 0x7e) {
+                 t = kAscii;
+             } else if (!(0x20 <= c && c <= 0x2f)) {
+                 goto Whoopsie;
+             }
+            break;
+        case kCsi1:
+            if (0x20 <= c && c <= 0x2f) {
+                t = kCsi2;
+            } else if (c == '[' && i == 3) {
+                /* linux function keys */
+            } else if (0x40 <= c && c <= 0x7e) {
+                t = kAscii;
+            } else if (!(0x30 <= c && c <= 0x3f)) {
+                goto Whoopsie;
+            }
+            break;
+        case kCsi2:
+            if (0x40 <= c && c <= 0x7e) {
+                t = kAscii;
+            } else if (!(0x20 <= c && c <= 0x2f)) {
+                goto Whoopsie;
+            }
+            break;
+        case kStr:
+            switch (c) {
+            case '\a':
+                t = kAscii;
+                break;
+            case 0033:
+            case 0302:
+                t = kStr2;
+                break;
+            default:
+                break;
+            }
+            break;
+        case kStr2:
+            switch (c) {
+            case '\a':
+            case '\\':
+            case 0234:
+                t = kAscii;
+                break;
+            default:
+                t = kStr;
+                break;
+            }
+            break;
+        default:
+            assert(0);
+        }
+    }
+    if (out_haswides) {
+        *out_haswides = haswides;
+    }
+    return w;
+}
+
+static int bestlineIsUnsupportedTerm(void) {
+    size_t i;
+    char *term;
+    static char once, res;
+    if (!once) {
+        if ((term = getenv("TERM"))) {
+            for (i = 0; i < sizeof(kUnsupported) / sizeof(*kUnsupported); i++) {
+                if (!CompareStrings(term,kUnsupported[i])) {
+                    res = 1;
+                    break;
+                }
+            }
+        }
+        once = 1;
+    }
+    return res;
+}
+
+static int enableRawMode(int fd) {
+    struct termios raw;
+    struct sigaction sa;
+    if (tcgetattr(fd,&orig_termios) != -1) {
+        raw = orig_termios;
+        raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+        raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+        raw.c_oflag &= ~OPOST;
+        raw.c_iflag |= IUTF8;
+        raw.c_cflag |= CS8;
+        raw.c_cc[VMIN] = 1;
+        raw.c_cc[VTIME] = 0;
+        if (tcsetattr(fd,TCSANOW,&raw) != -1) {
+            sa.sa_flags = 0;
+            sa.sa_handler = bestlineOnCont;
+            sigemptyset(&sa.sa_mask);
+            sigaction(SIGCONT,&sa,&orig_cont);
+            sa.sa_handler = bestlineOnWinch;
+            sigaction(SIGWINCH,&sa,&orig_winch);
+            rawmode = fd;
+            gotwinch = 0;
+            gotcont = 0;
+            return 0;
+        }
+    }
+    errno = ENOTTY;
+    return -1;
+}
+
+static void bestlineUnpause(int fd) {
+    if (ispaused) {
+        tcflow(fd, TCOON);
+        ispaused = 0;
+    }
+}
+
+void bestlineDisableRawMode(void) {
+    if (rawmode != -1) {
+        bestlineUnpause(rawmode);
+        sigaction(SIGCONT,&orig_cont,0);
+        sigaction(SIGWINCH,&orig_winch,0);
+        tcsetattr(rawmode,TCSANOW,&orig_termios);
+        rawmode = -1;
+    }
+}
+
+static int bestlineWrite(int fd, const void *p, size_t n) {
+    ssize_t rc;
+    size_t wrote;
+    do {
+        for (;;) {
+            if (gotint) {
+                errno = EINTR;
+                return -1;
+            }
+            if (ispaused) {
+                return 0;
+            }
+            rc = write(fd, p, n);
+            if (rc == -1 && errno == EINTR) {
+                continue;
+            } else if (rc == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+                if (WaitUntilReady(fd, POLLOUT) == -1) {
+                    if (errno == EINTR) {
+                        continue;
+                    } else {
+                        return -1;
+                    }
+                }
+            } else {
+                break;
+            }
+        }
+        if (rc != -1) {
+            wrote = rc;
+            n -= wrote;
+            p = (char *)p + wrote;
+        } else {
+            return -1;
+        }
+    } while (n);
+    return 0;
+}
+
+static int bestlineWriteStr(int fd, const char *p) {
+    return bestlineWrite(fd, p, strlen(p));
+}
+
+static ssize_t bestlineRead(int fd, char *buf, size_t size,
+                            struct bestlineState *l) {
+    size_t got;
+    ssize_t rc;
+    int refreshme;
+    do {
+        refreshme = 0;
+        if (gotint) {
+            errno = EINTR;
+            return -1;
+        }
+        if (gotcont && rawmode != -1) {
+            enableRawMode(rawmode);
+            if (l) refreshme = 1;
+        }
+        if (gotwinch && l) {
+            refreshme = 1;
+        }
+        if (refreshme) bestlineRefreshLine(l);
+        rc = ReadCharacter(fd, buf, size);
+    } while (rc == -1 && errno == EINTR);
+    if (rc != -1) {
+        got = rc;
+        if (got > 0 && l) {
+            memcpy(l->seq[1], l->seq[0], sizeof(l->seq[0]));
+            memset(l->seq[0], 0, sizeof(l->seq[0]));
+            memcpy(l->seq[0], buf, Min(Min(size, got), sizeof(l->seq[0]) - 1));
+        }
+    }
+    return rc;
+}
+
+/**
+ * Returns number of columns in current terminal.
+ *
+ * 1. Checks COLUMNS environment variable (set by Emacs)
+ * 2. Tries asking termios (works for pseudoteletypewriters)
+ * 3. Falls back to inband signalling (works w/ pipe or serial)
+ * 4. Otherwise we conservatively assume 80 columns
+ *
+ * @param ws should be initialized by caller to zero before first call
+ * @param ifd is input file descriptor
+ * @param ofd is output file descriptor
+ * @return window size
+ */
+static struct winsize GetTerminalSize(struct winsize ws, int ifd, int ofd) {
+    int x;
+    ssize_t n;
+    char *p, *s, b[16];
+    ioctl(ofd, TIOCGWINSZ, &ws);
+    if ((!ws.ws_row &&
+         (s = getenv("ROWS")) &&
+         (x = ParseUnsigned(s, 0)))) {
+        ws.ws_row = x;
+    }
+    if ((!ws.ws_col &&
+         (s = getenv("COLUMNS")) &&
+         (x = ParseUnsigned(s, 0)))) {
+        ws.ws_col = x;
+    }
+    if (((!ws.ws_col || !ws.ws_row) &&
+         bestlineRead(ifd,0,0,0) != -1 &&
+         bestlineWriteStr(ofd,
+             "\0337"           /* save position */
+             "\033[9979;9979H" /* move cursor to bottom right corner */
+             "\033[6n"         /* report position */
+             "\0338") != -1 && /* restore position */
+         (n = bestlineRead(ifd,b,sizeof(b),0)) != -1 &&
+         n && b[0] == 033 && b[1] == '[' && b[n - 1] == 'R')) {
+        p = b+2;
+        if ((x = ParseUnsigned(p,&p))) ws.ws_row = x;
+        if (*p++ == ';' && (x = ParseUnsigned(p,0))) ws.ws_col = x;
+    }
+    if (!ws.ws_col) ws.ws_col = 80;
+    if (!ws.ws_row) ws.ws_row = 24;
+    return ws;
+}
+
+/* Clear the screen. Used to handle ctrl+l */
+void bestlineClearScreen(int fd) {
+    bestlineWriteStr(fd,
+        "\033[H"    /* move cursor to top left corner */
+        "\033[2J"); /* erase display */
+}
+
+static void bestlineBeep(void) {
+    /* THE TERMINAL BELL IS DEAD - HISTORY HAS KILLED IT */
+}
+
+static char bestlineGrow(struct bestlineState *ls, size_t n) {
+    char *p;
+    size_t m;
+    m = ls->buflen;
+    if (m >= n) return 1;
+    do m += m >> 1;
+    while (m < n);
+    if (!(p = (char *)realloc(ls->buf, m * sizeof(*ls->buf)))) return 0;
+    ls->buf = p;
+    ls->buflen = m;
+    return 1;
+}
+
+/* This is an helper function for bestlineEdit() and is called when the
+ * user types the <tab> key in order to complete the string currently in the
+ * input.
+ *
+ * The state of the editing is encapsulated into the pointed bestlineState
+ * structure as described in the structure definition. */
+static ssize_t bestlineCompleteLine(struct bestlineState *ls, char *seq, int size) {
+    ssize_t nread;
+    size_t i, n, stop;
+    bestlineCompletions lc;
+    struct bestlineState saved;
+    nread=0;
+    memset(&lc,0,sizeof(lc));
+    completionCallback(ls->buf,&lc);
+    if (!lc.len) {
+        bestlineBeep();
+    } else {
+        i = 0;
+        stop = 0;
+        while (!stop) {
+            /* Show completion or original buffer */
+            if (i < lc.len) {
+                saved = *ls;
+                ls->len = ls->pos = strlen(lc.cvec[i]);
+                ls->buf = lc.cvec[i];
+                bestlineRefreshLine(ls);
+                ls->len = saved.len;
+                ls->pos = saved.pos;
+                ls->buf = saved.buf;
+            } else {
+                bestlineRefreshLine(ls);
+            }
+            if ((nread = bestlineRead(ls->ifd,seq,size,ls)) <= 0) {
+                bestlineFreeCompletions(&lc);
+                return -1;
+            }
+            switch (seq[0]) {
+            case '\t':
+                i = (i+1) % (lc.len+1);
+                if (i == lc.len) {
+                    bestlineBeep();
+                }
+                break;
+            default:
+                if (i < lc.len) {
+                    n = strlen(lc.cvec[i]);
+                    if (bestlineGrow(ls, n + 1)) {
+                        memcpy(ls->buf, lc.cvec[i], n + 1);
+                        ls->len = ls->pos = n;
+                    }
+                }
+                stop = 1;
+                break;
+            }
+        }
+    }
+    bestlineFreeCompletions(&lc);
+    return nread;
+}
+
+static void bestlineEditHistoryGoto(struct bestlineState *l, unsigned i) {
+    size_t n;
+    if (historylen <= 1) return;
+    i = Max(Min(i,historylen-1),0);
+    free(history[historylen - 1 - l->hindex]);
+    history[historylen - 1 - l->hindex] = strdup(l->buf);
+    l->hindex = i;
+    n = strlen(history[historylen - 1 - l->hindex]);
+    bestlineGrow(l, n + 1);
+    n = Min(n, l->buflen - 1);
+    memcpy(l->buf, history[historylen - 1 - l->hindex], n);
+    l->buf[n] = 0;
+    l->len = l->pos = n;
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditHistoryMove(struct bestlineState *l, int dx) {
+    bestlineEditHistoryGoto(l,l->hindex+dx);
+}
+
+static char *bestlineMakeSearchPrompt(struct abuf *ab, int fail, const char *s, int n) {
+    ab->len=0;
+    abAppendw(ab,'(');
+    if (fail) abAppends(ab,"failed ");
+    abAppends(ab,"reverse-i-search `\033[4m");
+    abAppend(ab,s,n);
+    abAppends(ab,"\033[24m");
+    abAppends(ab,s+n);
+    abAppendw(ab,Read32le("') "));
+    return ab->b;
+}
+
+static int bestlineSearch(struct bestlineState *l, char *seq, int size) {
+    char *p;
+    char isstale;
+    struct abuf ab;
+    struct abuf prompt;
+    unsigned i, j, k, matlen;
+    const char *oldprompt, *q;
+    int rc, fail, added, oldpos, oldindex;
+    if (historylen <= 1) return 0;
+    abInit(&ab);
+    abInit(&prompt);
+    oldpos = l->pos;
+    oldprompt = l->prompt;
+    oldindex = l->hindex;
+    for (fail=matlen=0;;) {
+        l->prompt = bestlineMakeSearchPrompt(&prompt,fail,ab.b,matlen);
+        bestlineRefreshLine(l);
+        fail = 1;
+        added = 0;
+        j = l->pos;
+        i = l->hindex;
+        rc = bestlineRead(l->ifd,seq,size,l);
+        if (rc > 0) {
+            if (seq[0] == Ctrl('?') || seq[0] == Ctrl('H')) {
+                if (ab.len) {
+                    --ab.len;
+                    matlen = Min(matlen, ab.len);
+                }
+            } else if (seq[0] == Ctrl('R')) {
+                if (j) {
+                    --j;
+                } else if (i + 1 < historylen) {
+                    ++i;
+                    j = strlen(history[historylen - 1 - i]);
+                }
+            } else if (seq[0] == Ctrl('G')) {
+                bestlineEditHistoryGoto(l,oldindex);
+                l->pos = oldpos;
+                rc = 0;
+                break;
+            } else if (IsControl(seq[0])) { /* only sees canonical c0 */
+                break;
+            } else {
+                abAppend(&ab,seq,rc);
+                added = rc;
+            }
+        } else {
+            break;
+        }
+        isstale = 0;
+        while (i < historylen) {
+            p = history[historylen - 1 - i];
+            k = strlen(p);
+            if (!isstale) {
+                j = Min(k, j + ab.len);
+            } else {
+                isstale = 0;
+                j = k;
+            }
+            if ((q = FindSubstringReverse(p, j, ab.b, ab.len))) {
+                bestlineEditHistoryGoto(l,i);
+                l->pos = q - p;
+                fail = 0;
+                if (added) {
+                    matlen += added;
+                    added = 0;
+                }
+                break;
+            } else {
+                isstale = 1;
+                ++i;
+            }
+        }
+    }
+    l->prompt = oldprompt;
+    bestlineRefreshLine(l);
+    abFree(&prompt);
+    abFree(&ab);
+    bestlineRefreshLine(l);
+    return rc;
+}
+
+static void bestlineRingFree(void) {
+    size_t i;
+    for (i = 0; i < BESTLINE_MAX_RING; ++i) {
+        if (ring.p[i]) {
+            free(ring.p[i]);
+            ring.p[i] = 0;
+        }
+    }
+}
+
+static void bestlineRingPush(const char *p, size_t n) {
+    char *q;
+    if (!n) return;
+    if (!(q = (char *)malloc(n + 1))) return;
+    ring.i = (ring.i + 1) % BESTLINE_MAX_RING;
+    free(ring.p[ring.i]);
+    ring.p[ring.i] = (char *)memcpy(q, p, n);
+    ring.p[ring.i][n] = 0;
+}
+
+static void bestlineRingRotate(void) {
+    size_t i;
+    for (i = 0; i < BESTLINE_MAX_RING; ++i) {
+        ring.i = (ring.i - 1) % BESTLINE_MAX_RING;
+        if (ring.p[ring.i]) break;
+    }
+}
+
+static char *bestlineRefreshHints(struct bestlineState *l) {
+    char *hint;
+    struct abuf ab;
+    const char *ansi1, *ansi2;
+    if (!hintsCallback) return 0;
+    if (!(hint = hintsCallback(l->buf, &ansi1, &ansi2))) return 0;
+    abInit(&ab);
+    ansi1 = "\033[90m";
+    ansi2 = "\033[39m";
+    if (ansi1) abAppends(&ab, ansi1);
+    abAppends(&ab, hint);
+    if (ansi2) abAppends(&ab, ansi2);
+    if (freeHintsCallback) freeHintsCallback(hint);
+    return ab.b;
+}
+
+static size_t Backward(struct bestlineState *l, size_t pos) {
+    if (pos) {
+        do --pos;
+        while (pos && (l->buf[pos] & 0300) == 0200);
+    }
+    return pos;
+}
+
+static int bestlineMirrorLeft(struct bestlineState *l, int res[2]) {
+    unsigned c, pos, left, right, depth, index;
+    if ((pos = Backward(l, l->pos))) {
+        right = GetUtf8(l->buf + pos, l->len - pos).c;
+        if ((left = GetMirrorLeft(right))) {
+            depth = 0;
+            index = pos;
+            do {
+                pos = Backward(l, pos);
+                c = GetUtf8(l->buf + pos, l->len - pos).c;
+                if (c == right) {
+                    ++depth;
+                } else if (c == left) {
+                    if (depth) {
+                        --depth;
+                    } else {
+                        res[0] = pos;
+                        res[1] = index;
+                        return 0;
+                    }
+                }
+            } while (pos);
+        }
+    }
+    return -1;
+}
+
+static int bestlineMirrorRight(struct bestlineState *l, int res[2]) {
+    struct rune rune;
+    unsigned pos, left, right, depth, index;
+    pos = l->pos;
+    rune = GetUtf8(l->buf + pos, l->len - pos);
+    left = rune.c;
+    if ((right = GetMirrorRight(left))) {
+        depth = 0;
+        index = pos;
+        do {
+            pos += rune.n;
+            rune = GetUtf8(l->buf + pos, l->len - pos);
+            if (rune.c == left) {
+                ++depth;
+            } else if (rune.c == right) {
+                if (depth) {
+                    --depth;
+                } else {
+                    res[0] = index;
+                    res[1] = pos;
+                    return 0;
+                }
+            }
+        } while (pos + rune.n < l->len);
+    }
+    return -1;
+}
+
+static int bestlineMirror(struct bestlineState *l, int res[2]) {
+    int rc;
+    rc = bestlineMirrorLeft(l, res);
+    if (rc == -1) rc = bestlineMirrorRight(l, res);
+    return rc;
+}
+
+static void bestlineRefreshLineImpl(struct bestlineState *l, int force) {
+    char *hint;
+    char flipit;
+    char hasflip;
+    char haswides;
+    struct abuf ab;
+    const char *buf;
+    struct rune rune;
+    struct winsize oldsize;
+    int fd, plen, rows, len, pos;
+    unsigned x, xn, yn, width, pwidth;
+    int i, t, cx, cy, tn, resized, flip[2];
+
+    /*
+     * synchonize the i/o state
+     */
+    if (ispaused) {
+        if (force) {
+            bestlineUnpause(l->ofd);
+        } else {
+            return;
+        }
+    }
+    if (!force && HasPendingInput(l->ifd)) {
+        l->dirty = 1;
+        return;
+    }
+    oldsize = l->ws;
+    if ((resized = gotwinch) && rawmode != -1) {
+        gotwinch = 0;
+        l->ws = GetTerminalSize(l->ws, l->ifd, l->ofd);
+    }
+    hasflip = !l->final && !bestlineMirror(l, flip);
+
+StartOver:
+    fd = l->ofd;
+    buf = l->buf;
+    pos = l->pos;
+    len = l->len;
+    xn = l->ws.ws_col;
+    yn = l->ws.ws_row;
+    plen = strlen(l->prompt);
+    pwidth = GetMonospaceWidth(l->prompt, plen, 0);
+    width = GetMonospaceWidth(buf, len, &haswides);
+
+    /*
+     * handle the case where the line is larger than the whole display
+     * gnu readline actually isn't able to deal with this situation!!!
+     * we kludge xn to address the edge case of wide chars on the edge
+     */
+    for (tn = xn - haswides * 2;;) {
+        if (pwidth + width + 1 < tn * yn) break; /* we're fine */
+        if (!len || width < 2) break;            /* we can't do anything */
+        if (pwidth + 2 > tn * yn) break;         /* we can't do anything */
+        if (pos > len / 2) {
+            /* hide content on the left if we're editing on the right */
+            rune = GetUtf8(buf, len);
+            buf += rune.n;
+            len -= rune.n;
+            pos -= rune.n;
+        } else {
+            /* hide content on the right if we're editing on left */
+            t = len;
+            while (len && (buf[len - 1] & 0300) == 0200) --len;
+            if (len) --len;
+            rune = GetUtf8(buf + len, t - len);
+        }
+        if ((t = GetMonospaceCharacterWidth(rune.c)) > 0) {
+            width -= t;
+        }
+    }
+    pos = Max(0, Min(pos, len));
+
+    /*
+     * now generate the terminal codes to update the line
+     *
+     * since we support unlimited lines it's important that we don't
+     * clear the screen before we draw the screen. doing that causes
+     * flickering. the key with terminals is to overwrite cells, and
+     * then use \e[K and \e[J to clear everything else.
+     *
+     * we make the assumption that prompts and hints may contain ansi
+     * sequences, but the buffer does not.
+     *
+     * we need to handle the edge case where a wide character like 度
+     * might be at the edge of the window, when there's one cell left.
+     * so we can't use division based on string width to compute the
+     * coordinates and have to track it as we go.
+     */
+    cy = -1;
+    cx = -1;
+    rows = 1;
+    abInit(&ab);
+    abAppendw(&ab, '\r'); /* start of line */
+    if (l->rows - l->oldpos - 1 > 0) {
+        abAppends(&ab, "\033[");
+        abAppendu(&ab, l->rows - l->oldpos - 1);
+        abAppendw(&ab, 'A'); /* cursor up clamped */
+    }
+    abAppends(&ab, l->prompt);
+    x = pwidth;
+    for (i = 0; i < len; i += rune.n) {
+        rune = GetUtf8(buf + i, len - i);
+        if (x && x + rune.n > xn) {
+            if (cy >= 0) ++cy;
+            abAppends(&ab, "\033[K"  /* clear line forward */
+                           "\r"      /* start of line */
+                           "\n");    /* cursor down unclamped */
+            ++rows;
+            x = 0;
+        }
+        if (i == pos) {
+            cy = 0;
+            cx = x;
+        }
+        if (maskmode) {
+            abAppendw(&ab, '*');
+        } else {
+            flipit = hasflip && (i == flip[0] || i == flip[1]);
+            if (flipit) abAppends(&ab, "\033[1m");
+            abAppendw(&ab, EncodeUtf8(rune.c));
+            if (flipit) abAppends(&ab, "\033[22m");
+        }
+        t = GetMonospaceCharacterWidth(rune.c);
+        t = Max(0, t);
+        x += t;
+    }
+    if (!l->final && (hint = bestlineRefreshHints(l))) {
+        if (GetMonospaceWidth(hint, strlen(hint), 0) < xn - x) {
+            if (cx < 0) {
+                cx = x;
+            }
+            abAppends(&ab, hint);
+        }
+        free(hint);
+    }
+    abAppendw(&ab, Read32le("\033[J")); /* erase display forwards */
+
+    /*
+     * if we are at the very end of the screen with our prompt, we need
+     * to emit a newline and move the prompt to the first column.
+     */
+    if (pos && pos == len && x >= xn) {
+        abAppendw(&ab, Read32le("\n\r\0"));
+        ++rows;
+    }
+
+    /*
+     * move cursor to right position
+     */
+    if (cy > 0) {
+        abAppends(&ab, "\033[");
+        abAppendu(&ab, cy);
+        abAppendw(&ab, 'A'); /* cursor up */
+    }
+    if (cx > 0) {
+        abAppendw(&ab, Read32le("\r\033["));
+        abAppendu(&ab, cx);
+        abAppendw(&ab, 'C'); /* cursor right */
+    } else if (!cx) {
+        abAppendw(&ab, '\r'); /* start */
+    }
+
+    /*
+     * now get ready to progress state
+     * we use a mostly correct kludge when the tty resizes
+     */
+    l->rows = rows;
+    if (resized && oldsize.ws_col > l->ws.ws_col) {
+        resized = 0;
+        abFree(&ab);
+        goto StartOver;
+    }
+    l->dirty = 0;
+    l->oldpos = Max(0, cy);
+
+    /*
+     * send codes to terminal
+     */
+    bestlineWrite(fd, ab.b, ab.len);
+    abFree(&ab);
+}
+
+static void bestlineRefreshLine(struct bestlineState *l) {
+    bestlineRefreshLineImpl(l, 0);
+}
+
+static void bestlineRefreshLineForce(struct bestlineState *l) {
+    bestlineRefreshLineImpl(l, 1);
+}
+
+static void bestlineEditInsert(struct bestlineState *l,
+                               const char *p, size_t n) {
+    if (!bestlineGrow(l, l->len + n + 1)) return;
+    memmove(l->buf + l->pos + n, l->buf + l->pos, l->len - l->pos);
+    memcpy(l->buf + l->pos, p, n);
+    l->pos += n;
+    l->len += n;
+    l->buf[l->len] = 0;
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditHome(struct bestlineState *l) {
+    l->pos = 0;
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditEnd(struct bestlineState *l) {
+    l->pos = l->len;
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditUp(struct bestlineState *l) {
+    bestlineEditHistoryMove(l,BESTLINE_HISTORY_PREV);
+}
+
+static void bestlineEditDown(struct bestlineState *l) {
+    bestlineEditHistoryMove(l,BESTLINE_HISTORY_NEXT);
+}
+
+static void bestlineEditBof(struct bestlineState *l) {
+    bestlineEditHistoryMove(l,BESTLINE_HISTORY_FIRST);
+}
+
+static void bestlineEditEof(struct bestlineState *l) {
+    bestlineEditHistoryMove(l,BESTLINE_HISTORY_LAST);
+}
+
+static void bestlineEditRefresh(struct bestlineState *l) {
+    bestlineClearScreen(l->ofd);
+    bestlineRefreshLine(l);
+}
+
+static size_t Forward(struct bestlineState *l, size_t pos) {
+    return pos + GetUtf8(l->buf + pos, l->len - pos).n;
+}
+
+static size_t Backwards(struct bestlineState *l, size_t pos, char pred(unsigned)) {
+    size_t i;
+    struct rune r;
+    while (pos) {
+        i = Backward(l, pos);
+        r = GetUtf8(l->buf + i, l->len - i);
+        if (pred(r.c)) {
+            pos = i;
+        } else {
+            break;
+        }
+    }
+    return pos;
+}
+
+static size_t Forwards(struct bestlineState *l, size_t pos, char pred(unsigned)) {
+    struct rune r;
+    while (pos < l->len) {
+        r = GetUtf8(l->buf + pos, l->len - pos);
+        if (pred(r.c)) {
+            pos += r.n;
+        } else {
+            break;
+        }
+    }
+    return pos;
+}
+
+static size_t ForwardWord(struct bestlineState *l, size_t pos) {
+    pos = Forwards(l, pos, IsSeparator);
+    pos = Forwards(l, pos, NotSeparator);
+    return pos;
+}
+
+static size_t BackwardWord(struct bestlineState *l, size_t pos) {
+    pos = Backwards(l, pos, IsSeparator);
+    pos = Backwards(l, pos, NotSeparator);
+    return pos;
+}
+
+static size_t EscapeWord(struct bestlineState *l) {
+    size_t i, j;
+    struct rune r;
+    for (i = l->pos; i && i < l->len; i += r.n) {
+        if (i < l->len) {
+            r = GetUtf8(l->buf + i, l->len - i);
+            if (IsSeparator(r.c)) break;
+        }
+        if ((j = i)) {
+            do --j;
+            while (j && (l->buf[j] & 0300) == 0200);
+            r = GetUtf8(l->buf + j, l->len - j);
+            if (IsSeparator(r.c)) break;
+        }
+    }
+    return i;
+}
+
+static void bestlineEditLeft(struct bestlineState *l) {
+    l->pos = Backward(l, l->pos);
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditRight(struct bestlineState *l) {
+    if (l->pos == l->len) return;
+    do l->pos++;
+    while (l->pos < l->len && (l->buf[l->pos] & 0300) == 0200);
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditLeftWord(struct bestlineState *l) {
+    l->pos = BackwardWord(l, l->pos);
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditRightWord(struct bestlineState *l) {
+    l->pos = ForwardWord(l, l->pos);
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditLeftExpr(struct bestlineState *l) {
+    int mark[2];
+    l->pos = Backwards(l, l->pos, IsXeparator);
+    if (!bestlineMirrorLeft(l, mark)) {
+        l->pos = mark[0];
+    } else {
+        l->pos = Backwards(l, l->pos, NotSeparator);
+    }
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditRightExpr(struct bestlineState *l) {
+    int mark[2];
+    l->pos = Forwards(l, l->pos, IsXeparator);
+    if (!bestlineMirrorRight(l, mark)) {
+        l->pos = Forward(l, mark[1]);
+    } else {
+        l->pos = Forwards(l, l->pos, NotSeparator);
+    }
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditDelete(struct bestlineState *l) {
+    size_t i;
+    if (l->pos == l->len) return;
+    i = Forward(l, l->pos);
+    memmove(l->buf+l->pos, l->buf+i, l->len-i+1);
+    l->len -= i - l->pos;
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditRubout(struct bestlineState *l) {
+    size_t i;
+    if (!l->pos) return;
+    i = Backward(l, l->pos);
+    memmove(l->buf+i, l->buf+l->pos, l->len-l->pos+1);
+    l->len -= l->pos - i;
+    l->pos = i;
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditDeleteWord(struct bestlineState *l) {
+    size_t i;
+    if (l->pos == l->len) return;
+    i = ForwardWord(l, l->pos);
+    bestlineRingPush(l->buf + l->pos, i - l->pos);
+    memmove(l->buf + l->pos, l->buf + i, l->len - i + 1);
+    l->len -= i - l->pos;
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditRuboutWord(struct bestlineState *l) {
+    size_t i;
+    if (!l->pos) return;
+    i = BackwardWord(l, l->pos);
+    bestlineRingPush(l->buf + i, l->pos - i);
+    memmove(l->buf + i, l->buf + l->pos, l->len - l->pos + 1);
+    l->len -= l->pos - i;
+    l->pos = i;
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditXlatWord(struct bestlineState *l, unsigned xlat(unsigned)) {
+    unsigned c;
+    size_t i, j;
+    struct rune r;
+    struct abuf ab;
+    abInit(&ab);
+    i = Forwards(l, l->pos, IsSeparator);
+    for (j = i; j < l->len; j += r.n) {
+        r = GetUtf8(l->buf + j, l->len - j);
+        if (IsSeparator(r.c)) break;
+        if ((c = xlat(r.c)) != r.c) {
+            abAppendw(&ab, EncodeUtf8(c));
+        } else { /* avoid canonicalization */
+            abAppend(&ab, l->buf + j, r.n);
+        }
+    }
+    if (ab.len && bestlineGrow(l, i + ab.len + l->len - j + 1)) {
+        l->pos = i + ab.len;
+        abAppend(&ab, l->buf + j, l->len - j);
+        l->len = i + ab.len;
+        memcpy(l->buf + i, ab.b, ab.len + 1);
+        bestlineRefreshLine(l);
+    }
+    abFree(&ab);
+}
+
+static void bestlineEditLowercaseWord(struct bestlineState *l) {
+    bestlineEditXlatWord(l, bestlineLowercase);
+}
+
+static void bestlineEditUppercaseWord(struct bestlineState *l) {
+    bestlineEditXlatWord(l, bestlineUppercase);
+}
+
+static void bestlineEditCapitalizeWord(struct bestlineState *l) {
+    iscapital = 0;
+    bestlineEditXlatWord(l, Capitalize);
+}
+
+static void bestlineEditKillLeft(struct bestlineState *l) {
+    size_t diff, old_pos;
+    bestlineRingPush(l->buf, l->pos);
+    old_pos = l->pos;
+    l->pos = 0;
+    diff = old_pos - l->pos;
+    memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1);
+    l->len -= diff;
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditKillRight(struct bestlineState *l) {
+    bestlineRingPush(l->buf + l->pos, l->len - l->pos);
+    l->buf[l->pos] = '\0';
+    l->len = l->pos;
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditYank(struct bestlineState *l) {
+    char *p;
+    size_t n;
+    if (!ring.p[ring.i]) return;
+    n = strlen(ring.p[ring.i]);
+    if (!bestlineGrow(l, l->len + n + 1)) return;
+    if (!(p = (char *)malloc(l->len - l->pos + 1))) return;
+    memcpy(p, l->buf + l->pos, l->len - l->pos + 1);
+    memcpy(l->buf + l->pos, ring.p[ring.i], n);
+    memcpy(l->buf + l->pos + n, p, l->len - l->pos + 1);
+    free(p);
+    l->yi = l->pos;
+    l->yj = l->pos + n;
+    l->pos += n;
+    l->len += n;
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditRotate(struct bestlineState *l) {
+    if ((l->seq[1][0] == Ctrl('Y') ||
+         (l->seq[1][0] == 033 && l->seq[1][1] == 'y'))) {
+        if (l->yi < l->len && l->yj <= l->len) {
+            memmove(l->buf + l->yi, l->buf + l->yj, l->len - l->yj + 1);
+            l->len -= l->yj - l->yi;
+            l->pos -= l->yj - l->yi;
+        }
+        bestlineRingRotate();
+        bestlineEditYank(l);
+    }
+}
+
+static void bestlineEditTranspose(struct bestlineState *l) {
+    char *q, *p;
+    size_t a, b, c;
+    b = l->pos;
+    a = Backward(l, b);
+    c = Forward(l, b);
+    if (!(a < b && b < c)) return;
+    p = q = (char *)malloc(c - a);
+    p = Copy(p, l->buf + b, c - b);
+    p = Copy(p, l->buf + a, b - a);
+    assert((size_t)(p - q) == c - a);
+    memcpy(l->buf + a, q, p - q);
+    l->pos = c;
+    free(q);
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditTransposeWords(struct bestlineState *l) {
+    char *q, *p;
+    size_t pi, xi, xj, yi, yj;
+    pi = EscapeWord(l);
+    xj = Backwards(l, pi, IsSeparator);
+    xi = Backwards(l, xj, NotSeparator);
+    yi = Forwards(l, pi, IsSeparator);
+    yj = Forwards(l, yi, NotSeparator);
+    if (!(xi < xj && xj < yi && yi < yj)) return;
+    p = q = (char *)malloc(yj - xi);
+    p = Copy(p, l->buf + yi, yj - yi);
+    p = Copy(p, l->buf + xj, yi - xj);
+    p = Copy(p, l->buf + xi, xj - xi);
+    assert((size_t)(p - q) == yj - xi);
+    memcpy(l->buf + xi, q, p - q);
+    l->pos = yj;
+    free(q);
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditSqueeze(struct bestlineState *l) {
+    size_t i, j;
+    i = Backwards(l, l->pos, IsSeparator);
+    j = Forwards(l, l->pos, IsSeparator);
+    if (!(i < j)) return;
+    memmove(l->buf + i, l->buf + j, l->len - j + 1);
+    l->len -= j - i;
+    l->pos  = i;
+    bestlineRefreshLine(l);
+}
+
+static void bestlineEditMark(struct bestlineState *l) {
+    l->mark = l->pos;
+}
+
+static void bestlineEditGoto(struct bestlineState *l) {
+    if (l->mark > l->len) return;
+    l->pos = Min(l->mark, l->len);
+    bestlineRefreshLine(l);
+}
+
+static size_t bestlineEscape(char *d, const char *s, size_t n) {
+    char *p;
+    size_t i;
+    unsigned c, w, l;
+    for (p = d, l = i = 0; i < n; ++i) {
+        switch ((c = s[i] & 255)) {
+            Case('\a', w = Read16le("\\a"));
+            Case('\b', w = Read16le("\\b"));
+            Case('\t', w = Read16le("\\t"));
+            Case('\n', w = Read16le("\\n"));
+            Case('\v', w = Read16le("\\v"));
+            Case('\f', w = Read16le("\\f"));
+            Case('\r', w = Read16le("\\r"));
+            Case('"',  w = Read16le("\\\""));
+            Case('\'', w = Read16le("\\\'"));
+            Case('\\', w = Read16le("\\\\"));
+            default:
+                if (c <= 0x1F || c == 0x7F ||
+                    (c == '?' && l == '?')) {
+                    w = Read16le("\\x");
+                    w |= "0123456789abcdef"[(c & 0xF0) >> 4] << 020;
+                    w |= "0123456789abcdef"[(c & 0x0F) >> 0] << 030;
+                } else {
+                    w = c;
+                }
+                break;
+        }
+        p[0] = (w & 0x000000ff) >> 000;
+        p[1] = (w & 0x0000ff00) >> 010;
+        p[2] = (w & 0x00ff0000) >> 020;
+        p[3] = (w & 0xff000000) >> 030;
+        p += (Bsr(w) >> 3) + 1;
+        l = w;
+    }
+    return p - d;
+}
+
+static void bestlineEditInsertEscape(struct bestlineState *l) {
+    size_t m;
+    ssize_t n;
+    char seq[16];
+    char esc[sizeof(seq) * 4];
+    if ((n = bestlineRead(l->ifd, seq, sizeof(seq), l)) > 0) {
+        m = bestlineEscape(esc, seq, n);
+        bestlineEditInsert(l, esc, m);
+    }
+}
+
+static void bestlineEditInterrupt(void) {
+    gotint = SIGINT;
+}
+
+static void bestlineEditQuit(void) {
+    gotint = SIGQUIT;
+}
+
+static void bestlineEditSuspend(void) {
+    raise(SIGSTOP);
+}
+
+static void bestlineEditPause(struct bestlineState *l) {
+    tcflow(l->ofd, TCOOFF);
+    ispaused = 1;
+}
+
+static void bestlineEditCtrlq(struct bestlineState *l) {
+    if (ispaused) {
+        bestlineUnpause(l->ofd);
+        bestlineRefreshLineForce(l);
+    } else {
+        bestlineEditInsertEscape(l);
+    }
+}
+
+/**
+ * Runs bestline engine.
+ *
+ * This function is the core of the line editing capability of bestline.
+ * It expects 'fd' to be already in "raw mode" so that every key pressed
+ * will be returned ASAP to read().
+ *
+ * The resulting string is put into 'buf' when the user type enter, or
+ * when ctrl+d is typed.
+ *
+ * Returns chomped character count in buf >=0 or -1 on eof / error
+ */
+static ssize_t bestlineEdit(int stdin_fd, int stdout_fd, const char *prompt,
+                            char **obuf) {
+    ssize_t rc;
+    size_t nread;
+    char *p, seq[16];
+    struct rune rune;
+    unsigned long long w;
+    struct bestlineState l;
+    memset(&l,0,sizeof(l));
+    if (!(l.buf = (char *)malloc((l.buflen = 32)))) return -1;
+    l.buf[0] = 0;
+    l.ifd = stdin_fd;
+    l.ofd = stdout_fd;
+    l.prompt = prompt ? prompt : "";
+    l.ws = GetTerminalSize(l.ws,l.ifd,l.ofd);
+    bestlineHistoryAdd("");
+    bestlineWriteStr(l.ofd,l.prompt);
+    while (1) {
+        if (l.dirty) bestlineRefreshLineForce(&l);
+        rc = bestlineRead(l.ifd,seq,sizeof(seq),&l);
+        if (rc > 0) {
+            if (seq[0] == Ctrl('R')) {
+                rc = bestlineSearch(&l,seq,sizeof(seq));
+                if (!rc) continue;
+            } else if (seq[0] == '\t' && completionCallback) {
+                rc = bestlineCompleteLine(&l,seq,sizeof(seq));
+                if (!rc) continue;
+            }
+        }
+        if (rc > 0) {
+            nread = rc;
+        } else if (!rc && l.len) {
+            nread = 1;
+            seq[0] = '\r';
+            seq[1] = 0;
+        } else {
+            free(history[--historylen]);
+            history[historylen] = 0;
+            free(l.buf);
+            return -1;
+        }
+        switch (seq[0]) {
+        Case(Ctrl('P'), bestlineEditUp(&l));
+        Case(Ctrl('E'), bestlineEditEnd(&l));
+        Case(Ctrl('N'), bestlineEditDown(&l));
+        Case(Ctrl('A'), bestlineEditHome(&l));
+        Case(Ctrl('B'), bestlineEditLeft(&l));
+        Case(Ctrl('@'), bestlineEditMark(&l));
+        Case(Ctrl('Y'), bestlineEditYank(&l));
+        Case(Ctrl('Q'), bestlineEditCtrlq(&l));
+        Case(Ctrl('F'), bestlineEditRight(&l));
+        Case(Ctrl('\\'), bestlineEditQuit());
+        Case(Ctrl('S'), bestlineEditPause(&l));
+        Case(Ctrl('?'), bestlineEditRubout(&l));
+        Case(Ctrl('H'), bestlineEditRubout(&l));
+        Case(Ctrl('L'), bestlineEditRefresh(&l));
+        Case(Ctrl('Z'), bestlineEditSuspend());
+        Case(Ctrl('U'), bestlineEditKillLeft(&l));
+        Case(Ctrl('C'), bestlineEditInterrupt());
+        Case(Ctrl('T'), bestlineEditTranspose(&l));
+        Case(Ctrl('K'), bestlineEditKillRight(&l));
+        Case(Ctrl('W'), bestlineEditRuboutWord(&l));
+        case Ctrl('X'):
+            if (l.seq[1][0] == Ctrl('X')) {
+                bestlineEditGoto(&l);
+            }
+            break;
+        case Ctrl('D'):
+            if (l.len) {
+                bestlineEditDelete(&l);
+            } else {
+                free(history[--historylen]);
+                history[historylen] = 0;
+                free(l.buf);
+                return -1;
+            }
+            break;
+        case '\r':
+            l.final = 1;
+            free(history[--historylen]);
+            history[historylen] = 0;
+            bestlineEditEnd(&l);
+            bestlineRefreshLineForce(&l);
+            if ((p = (char *)realloc(l.buf, l.len + 1))) l.buf = p;
+            *obuf = l.buf;
+            return l.len;
+        case 033:
+            if (nread < 2) break;
+            switch (seq[1]) {
+            Case('<', bestlineEditBof(&l));
+            Case('>', bestlineEditEof(&l));
+            Case('y', bestlineEditRotate(&l));
+            Case('\\', bestlineEditSqueeze(&l));
+            Case('b', bestlineEditLeftWord(&l));
+            Case('f', bestlineEditRightWord(&l));
+            Case('h', bestlineEditRuboutWord(&l));
+            Case('d', bestlineEditDeleteWord(&l));
+            Case('l', bestlineEditLowercaseWord(&l));
+            Case('u', bestlineEditUppercaseWord(&l));
+            Case('c', bestlineEditCapitalizeWord(&l));
+            Case('t', bestlineEditTransposeWords(&l));
+            Case(Ctrl('B'), bestlineEditLeftExpr(&l));
+            Case(Ctrl('F'), bestlineEditRightExpr(&l));
+            Case(Ctrl('H'), bestlineEditRuboutWord(&l));
+            case '[':
+                if (nread < 3) break;
+                if (seq[2] >= '0' && seq[2] <= '9') {
+                    if (nread < 4) break;
+                    if (seq[3] == '~') {
+                        switch (seq[2]) {
+                        Case('1', bestlineEditHome(&l));   /* \e[1~ */
+                        Case('3', bestlineEditDelete(&l)); /* \e[3~ */
+                        Case('4', bestlineEditEnd(&l));    /* \e[4~ */
+                        default:
+                            break;
+                        }
+                    }
+                } else {
+                    switch (seq[2]) {
+                    Case('A', bestlineEditUp(&l));
+                    Case('B', bestlineEditDown(&l));
+                    Case('C', bestlineEditRight(&l));
+                    Case('D', bestlineEditLeft(&l));
+                    Case('H', bestlineEditHome(&l));
+                    Case('F', bestlineEditEnd(&l));
+                    default:
+                        break;
+                    }
+                }
+                break;
+            case 'O':
+                if (nread < 3) break;
+                switch (seq[2]) {
+                Case('A', bestlineEditUp(&l));
+                Case('B', bestlineEditDown(&l));
+                Case('C', bestlineEditRight(&l));
+                Case('D', bestlineEditLeft(&l));
+                Case('H', bestlineEditHome(&l));
+                Case('F', bestlineEditEnd(&l));
+                default:
+                    break;
+                }
+                break;
+            case 033:
+                if (nread < 3) break;
+                switch (seq[2]) {
+                case '[':
+                    if (nread < 4) break;
+                    switch (seq[3]) {
+                        Case('C', bestlineEditRightExpr(&l)); /* \e\e[C alt-right */
+                        Case('D', bestlineEditLeftExpr(&l));  /* \e\e[D alt-left */
+                        default:
+                            break;
+                    }
+                    break;
+                case 'O':
+                    if (nread < 4) break;
+                    switch (seq[3]) {
+                        Case('C', bestlineEditRightExpr(&l)); /* \e\eOC alt-right */
+                        Case('D', bestlineEditLeftExpr(&l));  /* \e\eOD alt-left */
+                        default:
+                            break;
+                    }
+                    break;
+                default:
+                    break;
+                }
+                break;
+            default:
+                break;
+            }
+            break;
+        default:
+            if (!IsControl(seq[0])) { /* only sees canonical c0 */
+                if (xlatCallback) {
+                    rune = GetUtf8(seq,nread);
+                    w = EncodeUtf8(xlatCallback(rune.c));
+                    nread = 0;
+                    do {
+                        seq[nread++] = w;
+                    } while ((w >>= 8));
+                }
+                bestlineEditInsert(&l,seq,nread);
+            }
+            break;
+        }
+    }
+}
+
+void bestlineFree(void *ptr) {
+    free(ptr);
+}
+
+void bestlineHistoryFree(void) {
+    size_t i;
+    for (i = 0; i < BESTLINE_MAX_HISTORY; i++) {
+        if (history[i]) {
+            free(history[i]);
+            history[i] = 0;
+        }
+    }
+    historylen = 0;
+}
+
+static void bestlineAtExit(void) {
+    bestlineDisableRawMode();
+    bestlineHistoryFree();
+    bestlineRingFree();
+}
+
+int bestlineHistoryAdd(const char *line) {
+    char *linecopy;
+    if (!BESTLINE_MAX_HISTORY) return 0;
+    if (historylen && !strcmp(history[historylen-1], line)) return 0;
+    if (!(linecopy = strdup(line))) return 0;
+    if (historylen == BESTLINE_MAX_HISTORY) {
+        free(history[0]);
+        memmove(history,history+1,sizeof(char*)*(BESTLINE_MAX_HISTORY-1));
+        historylen--;
+    }
+    history[historylen++] = linecopy;
+    return 1;
+}
+
+/**
+ * Saves line editing history to file.
+ *
+ * @return 0 on success, or -1 w/ errno
+ */
+int bestlineHistorySave(const char *filename) {
+    FILE *fp;
+    unsigned j;
+    mode_t old_umask;
+    old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
+    fp = fopen(filename,"w");
+    umask(old_umask);
+    if (!fp) return -1;
+    chmod(filename,S_IRUSR|S_IWUSR);
+    for (j = 0; j < historylen; j++) {
+        fputs(history[j],fp);
+        fputc('\n',fp);
+    }
+    fclose(fp);
+    return 0;
+}
+
+/**
+ * Loads history from the specified file.
+ *
+ * If the file doesn't exist, zero is returned and this will do nothing.
+ * If the file does exists and the operation succeeded zero is returned
+ * otherwise on error -1 is returned.
+ *
+ * @return 0 on success, or -1 w/ errno
+ */
+int bestlineHistoryLoad(const char *filename) {
+    char **h;
+    int rc, fd, err;
+    size_t i, j, k, n, t;
+    char *m, *e, *p, *q, *f, *s;
+    err = errno, rc = 0;
+    if (!BESTLINE_MAX_HISTORY) return 0;
+    if (!(h = (char**)calloc(2*BESTLINE_MAX_HISTORY,sizeof(char*)))) return -1;
+    if ((fd = open(filename,O_RDONLY)) != -1) {
+        if ((n = GetFdSize(fd))) {
+            if ((m = (char *)mmap(0,n,PROT_READ,MAP_SHARED,fd,0))!=MAP_FAILED) {
+                for (i = 0, e = (p = m) + n; p < e; p = f + 1) {
+                    if (!(q = (char *)memchr(p, '\n', e - p))) q = e;
+                    for (f = q; q > p; --q) {
+                        if (q[-1] != '\n' && q[-1] != '\r') break;
+                    }
+                    if (q > p) {
+                        h[i * 2 + 0] = p;
+                        h[i * 2 + 1] = q;
+                        i = (i + 1) % BESTLINE_MAX_HISTORY;
+                    }
+                }
+                bestlineHistoryFree();
+                for (j = 0; j < BESTLINE_MAX_HISTORY; ++j) {
+                    if (h[(k = (i + j) % BESTLINE_MAX_HISTORY) * 2]) {
+                        if ((s = (char *)malloc((t=h[k*2+1]-h[k*2])+1))) {
+                            memcpy(s,h[k*2],t),s[t]=0;
+                            history[historylen++] = s;
+                        }
+                    }
+                }
+                munmap(m,n);
+            } else {
+                rc = -1;
+            }
+        }
+        close(fd);
+    } else if (errno == ENOENT) {
+        errno = err;
+    } else {
+        rc = -1;
+    }
+    free(h);
+    return rc;
+}
+
+/**
+ * Reads line interactively.
+ *
+ * This function can be used instead of bestline() in cases where we
+ * know for certain we're dealing with a terminal, which means we can
+ * avoid linking any stdio code.
+ *
+ * @return chomped allocated string of read line or null on eof/error
+ */
+char *bestlineRaw(const char *prompt, int infd, int outfd) {
+    char *buf;
+    ssize_t rc;
+    static char once;
+    struct sigaction sa[3];
+    if (!once) atexit(bestlineAtExit), once = 1;
+    if (enableRawMode(infd) == -1) return 0;
+    buf = 0;
+    gotint = 0;
+    sigemptyset(&sa->sa_mask);
+    sa->sa_flags = 0;
+    sa->sa_handler = bestlineOnInt;
+    sigaction(SIGINT,sa,sa+1);
+    sigaction(SIGQUIT,sa,sa+2);
+    rc = bestlineEdit(infd,outfd,prompt,&buf);
+    bestlineDisableRawMode();
+    sigaction(SIGQUIT,sa+2,0);
+    sigaction(SIGINT,sa+1,0);
+    if (gotint) {
+        free(buf);
+        buf = 0;
+        raise(gotint);
+        errno = EINTR;
+        rc = -1;
+    }
+    if (rc != -1) {
+        bestlineWriteStr(outfd,"\n");
+        return buf;
+    } else {
+        free(buf);
+        return 0;
+    }
+}
+
+/**
+ * Reads line intelligently.
+ *
+ * The high level function that is the main API of the bestline library.
+ * This function checks if the terminal has basic capabilities, just checking
+ * for a blacklist of inarticulate terminals, and later either calls the line
+ * editing function or uses dummy fgets() so that you will be able to type
+ * something even in the most desperate of the conditions.
+ *
+ * @param prompt is printed before asking for input if we have a term
+ *     and this may be set to empty or null to disable and prompt may
+ *     contain ansi escape sequences, color, utf8, etc.
+ * @return chomped allocated string of read line or null on eof/error
+ */
+char *bestline(const char *prompt) {
+    if (prompt && *prompt &&
+        (strchr(prompt, '\n') || strchr(prompt, '\t') ||
+         strchr(prompt + 1, '\r'))) {
+        errno = EINVAL;
+        return 0;
+    }
+    if ((!isatty(fileno(stdin)) ||
+         !isatty(fileno(stdout)))) {
+        if (prompt && *prompt && (IsCharDev(fileno(stdin)) &&
+                                  IsCharDev(fileno(stdout)))) {
+            fputs(prompt,stdout);
+            fflush(stdout);
+        }
+        return GetLine(stdin, stdout);
+    } else if (bestlineIsUnsupportedTerm()) {
+        if (prompt && *prompt) {
+            fputs(prompt,stdout);
+            fflush(stdout);
+        }
+        return GetLine(stdin, stdout);
+    } else {
+        fflush(stdout);
+        return bestlineRaw(prompt,fileno(stdin),fileno(stdout));
+    }
+}
+
+/**
+ * Reads line intelligently w/ history, e.g.
+ *
+ *     // see ~/.foo_history
+ *     main() {
+ *         char *line;
+ *         while ((line = bestlineWithHistory("IN> ", "foo"))) {
+ *             printf("OUT> %s\n", line);
+ *             free(line);
+ *         }
+ *     }
+ *
+ * @param prompt is printed before asking for input if we have a term
+ *     and this may be set to empty or null to disable and prompt may
+ *     contain ansi escape sequences, color, utf8, etc.
+ * @param prog is name of your app, used to generate history filename
+ *     however if it contains a slash / dot then we'll assume prog is
+ *     the history filename which as determined by the caller
+ * @return chomped allocated string of read line or null on eof/error
+ */
+char *bestlineWithHistory(const char *prompt, const char *prog) {
+    char *line;
+    struct abuf path;
+    const char *a, *b;
+    abInit(&path);
+    if (prog) {
+        if (strchr(prog, '/') || strchr(prog, '.')) {
+            abAppends(&path, prog);
+        } else {
+            b = "";
+            if (!(a = getenv("HOME"))) {
+                if (!(a = getenv("HOMEDRIVE")) ||
+                    !(b = getenv("HOMEPATH"))) {
+                    a = "";
+                }
+            }
+            if (*a) {
+                abAppends(&path, a);
+                abAppends(&path, b);
+                abAppendw(&path, '/');
+            }
+            abAppendw(&path, '.');
+            abAppends(&path, prog);
+            abAppends(&path, "_history");
+        }
+    }
+    if (path.len) {
+        bestlineHistoryLoad(path.b);
+    }
+    line = bestline(prompt);
+    if (path.len && line && *line) {
+        /* history here is inefficient but helpful when the user has multiple
+         * repls open at the same time, so history propagates between them */
+        bestlineHistoryLoad(path.b);
+        bestlineHistoryAdd(line);
+        bestlineHistorySave(path.b);
+    }
+    abFree(&path);
+    return line;
+}
+
+/**
+ * Registers tab completion callback.
+ */
+void bestlineSetCompletionCallback(bestlineCompletionCallback *fn) {
+    completionCallback = fn;
+}
+
+/**
+ * Registers hints callback.
+ *
+ * Register a hits function to be called to show hits to the user at the
+ * right of the prompt.
+ */
+void bestlineSetHintsCallback(bestlineHintsCallback *fn) {
+    hintsCallback = fn;
+}
+
+/**
+ * Sets free hints callback.
+ *
+ * This registers a function to free the hints returned by the hints
+ * callback registered with bestlineSetHintsCallback().
+ */
+void bestlineSetFreeHintsCallback(bestlineFreeHintsCallback *fn) {
+    freeHintsCallback = fn;
+}
+
+/**
+ * Sets character translation callback.
+ */
+void bestlineSetXlatCallback(unsigned fn(unsigned)) {
+    xlatCallback = fn;
+}
+
+/**
+ * Adds completion.
+ *
+ * This function is used by the callback function registered by the user
+ * in order to add completion options given the input string when the
+ * user typed <tab>. See the example.c source code for a very easy to
+ * understand example.
+ */
+void bestlineAddCompletion(bestlineCompletions *lc, const char *str) {
+    size_t len;
+    char *copy, **cvec;
+    if ((copy = (char *)malloc((len = strlen(str))+1))) {
+        memcpy(copy,str,len+1);
+        if ((cvec = (char **)realloc(lc->cvec,(lc->len+1)*sizeof(*lc->cvec)))) {
+            lc->cvec = cvec;
+            lc->cvec[lc->len++] = copy;
+        } else {
+            free(copy);
+        }
+    }
+}
+
+/**
+ * Frees list of completion option populated by bestlineAddCompletion().
+ */
+void bestlineFreeCompletions(bestlineCompletions *lc) {
+    size_t i;
+    for (i = 0; i < lc->len; i++)
+        free(lc->cvec[i]);
+    if (lc->cvec)
+        free(lc->cvec);
+}
+
+/**
+ * Enables "mask mode".
+ *
+ * When it is enabled, instead of the input that the user is typing, the
+ * terminal will just display a corresponding number of asterisks, like
+ * "****". This is useful for passwords and other secrets that should
+ * not be displayed.
+ *
+ * @see bestlineMaskModeDisable()
+ */
+void bestlineMaskModeEnable(void) {
+    maskmode = 1;
+}
+
+/**
+ * Disables "mask mode".
+ */
+void bestlineMaskModeDisable(void) {
+    maskmode = 0;
+}

+ 33 - 0
bestline.h

@@ -0,0 +1,33 @@
+#pragma once
+
+typedef struct bestlineCompletions {
+  unsigned long len;
+  char **cvec;
+} bestlineCompletions;
+
+typedef void(bestlineCompletionCallback)(const char *, bestlineCompletions *);
+typedef char *(bestlineHintsCallback)(const char *, const char **,
+                                       const char **);
+typedef void(bestlineFreeHintsCallback)(void *);
+
+void bestlineSetCompletionCallback(bestlineCompletionCallback *);
+void bestlineSetHintsCallback(bestlineHintsCallback *);
+void bestlineSetFreeHintsCallback(bestlineFreeHintsCallback *);
+void bestlineAddCompletion(bestlineCompletions *, const char *);
+
+char *bestline(const char *);
+char *bestlineRaw(const char *, int, int);
+char *bestlineWithHistory(const char *, const char *);
+int bestlineHistoryAdd(const char *);
+int bestlineHistorySave(const char *);
+int bestlineHistoryLoad(const char *);
+void bestlineFreeCompletions(bestlineCompletions *);
+void bestlineHistoryFree(void);
+void bestlineClearScreen(int);
+void bestlineMaskModeEnable(void);
+void bestlineMaskModeDisable(void);
+void bestlineDisableRawMode(void);
+void bestlineFree(void *);
+unsigned bestlineLowercase(unsigned);
+unsigned bestlineUppercase(unsigned);
+void bestlineSetXlatCallback(unsigned(*)(unsigned));

BIN
bin/footprint.png


BIN
bin/lisp.elf.linux


+ 173 - 166
lisp.c

@@ -16,110 +16,109 @@
 │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
 │ PERFORMANCE OF THIS SOFTWARE.                                                │
 ╚─────────────────────────────────────────────────────────────────────────────*/
-#include "lisp.h"
+#include "bestline.h"
 
-#define RETRO  1  // auto capitalize input
-#define DELETE 1  // allow backspace to rub out symbol
-#define QUOTES 1  // allow 'X shorthand (QUOTE X)
-#define PROMPT 1  // show repl prompt
-#define WORD   short
-#define WORDS  8192
+#ifndef __COSMOPOLITAN__
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#endif
+
+#define QUOTES 1     /* allow 'X shorthand for (QUOTE X) */
+#define FUNDEF 1     /* be friendly w/undefined behavior */
+#define TRACE  0     /* prints Eval() arguments / result */
 
 /*───────────────────────────────────────────────────────────────────────────│─╗
 │ The LISP Challenge § LISP Machine                                        ─╬─│┼
 ╚────────────────────────────────────────────────────────────────────────────│*/
 
-#define ATOM 1
-#define CONS 0
-
-#define NIL         (ATOM | 0)
-#define UNDEFINED   (ATOM | 8)
-#define ATOM_T      (ATOM | 30)
-#define ATOM_QUOTE  (ATOM | 34)
-#define ATOM_COND   (ATOM | 46)
-#define ATOM_ATOM   (ATOM | 56)
-#define ATOM_CAR    (ATOM | 66)
-#define ATOM_CDR    (ATOM | 74)
-#define ATOM_CONS   (ATOM | 82)
-#define ATOM_EQ     (ATOM | 92)
-#define ATOM_LAMBDA (ATOM | 98)
-
-#define VALUE(x) ((x) >> 1)
+#define ATOM 0
+#define CONS 1
+
+#define ISATOM(x)   (~(x)&1)
+#define VALUE(x)    ((x)>>1)
+#define OBJECT(t,v) ((v)<<1|(t))
+
+#define NIL         OBJECT(ATOM,0)
+#define ATOM_T      OBJECT(ATOM,4)
+#define ATOM_QUOTE  OBJECT(ATOM,6)
+#define ATOM_COND   OBJECT(ATOM,12)
+#define ATOM_ATOM   OBJECT(ATOM,17)
+#define ATOM_CAR    OBJECT(ATOM,22)
+#define ATOM_CDR    OBJECT(ATOM,26)
+#define ATOM_CONS   OBJECT(ATOM,30)
+#define ATOM_EQ     OBJECT(ATOM,35)
+#define ATOM_LAMBDA OBJECT(ATOM,38)
+#define UNDEFINED   OBJECT(ATOM,45)
 
 struct Lisp {
-  WORD mem[WORDS];
+  int mem[8192];
   unsigned char syntax[256];
-  WORD look;
-  WORD globals;
-  WORD index;
+  int look;
+  int globals;
+  int index;
   char token[128];
-  char str[WORDS];
+  char str[8192];
 };
 
-_Static_assert(sizeof(struct Lisp) <= 0x7c00 - 0x600,
-               "LISP Machine too large for real mode");
-
-_Alignas(char) const char kSymbols[] = "NIL\0"
-                                       "*UNDEFINED\0"
-                                       "T\0"
-                                       "QUOTE\0"
-                                       "COND\0"
-                                       "ATOM\0"
-                                       "CAR\0"
-                                       "CDR\0"
-                                       "CONS\0"
-                                       "EQ\0"
-                                       "LAMBDA";
-
-#ifdef __REAL_MODE__
-static struct Lisp *const q;
-#else
-static struct Lisp q[1];
+static const char kSymbols[] =
+    "NIL\0"
+    "T\0"
+    "QUOTE\0"
+    "COND\0"
+    "ATOM\0"
+    "CAR\0"
+    "CDR\0"
+    "CONS\0"
+    "EQ\0"
+    "LAMBDA\0"
+#if FUNDEF
+    "*UNDEFINED"
 #endif
+;
+
+static struct Lisp q[1];
 
-static void Print(long);
-static WORD GetList(void);
-static WORD GetObject(void);
-static void PrintObject(long);
-static WORD Eval(WORD, WORD);
+static void Print(int);
+static int GetList(void);
+static int GetObject(void);
+static void PrintObject(int);
+static int Eval(int, int);
 
 static void SetupSyntax(void) {
-  unsigned char *syntax = q->syntax;
-  asm("" : "+bSD"(syntax));
-  syntax[' '] = ' ';
-  syntax['\r'] = ' ';
-  syntax['\n'] = ' ';
-  syntax['('] = '(';
-  syntax[')'] = ')';
-  syntax['.'] = '.';
-#if QUOTES
-  syntax['\''] = '\'';
-#endif
+  q->syntax[' '] = ' ';
+  q->syntax['\r'] = ' ';
+  q->syntax['\n'] = ' ';
+  q->syntax['('] = '(';
+  q->syntax[')'] = ')';
+  q->syntax['.'] = '.';
+  q->syntax['\''] = '\'';
 }
 
 static void SetupBuiltins(void) {
-  CopyMemory(q->str, kSymbols, sizeof(kSymbols));
+  memmove(q->str, kSymbols, sizeof(kSymbols));
 }
 
-static inline WORD Car(long x) {
-  return PEEK_ARRAY(q, mem, VALUE(x), 0);
+static inline int Car(int x) {
+  return q->mem[VALUE(x) + 0];
 }
 
-static inline WORD Cdr(long x) {
-  return PEEK_ARRAY(q, mem, VALUE(x), 1);
+static inline int Cdr(int x) {
+  return q->mem[VALUE(x) + 1];
 }
 
-static WORD Set(long i, long k, long v) {
-  POKE_ARRAY(q, mem, VALUE(i), 0, k);
-  POKE_ARRAY(q, mem, VALUE(i), 1, v);
+static int Set(int i, int k, int v) {
+  q->mem[VALUE(i) + 0] = k;
+  q->mem[VALUE(i) + 1] = v;
   return i;
 }
 
-static WORD Cons(WORD car, WORD cdr) {
+static int Cons(int car, int cdr) {
   int i, cell;
   i = q->index;
-  POKE_ARRAY(q, mem, i, 0, car);
-  POKE_ARRAY(q, mem, i, 1, cdr);
+  q->mem[i + 0] = car;
+  q->mem[i + 1] = cdr;
   q->index = i + 2;
   cell = OBJECT(CONS, i);
   return cell;
@@ -128,120 +127,116 @@ static WORD Cons(WORD car, WORD cdr) {
 static char *StpCpy(char *d, char *s) {
   char c;
   do {
-    c = LODS(s);  // a.k.a. c = *s++
-    STOS(d, c);   // a.k.a. *d++ = c
+    c = *s++;
+    *d++ = c;
   } while (c);
   return d;
 }
 
-static WORD Intern(char *s) {
+static int Intern(char *s) {
   int j, cx;
   char c, *z, *t;
   z = q->str;
-  c = LODS(z);
+  c = *z++;
   while (c) {
     for (j = 0;; ++j) {
-      if (c != PEEK(s, j, 0)) {
+      if (c != s[j]) {
         break;
       }
       if (!c) {
         return OBJECT(ATOM, z - q->str - j - 1);
       }
-      c = LODS(z);
+      c = *z++;
     }
-    while (c) c = LODS(z);
-    c = LODS(z);
+    while (c) c = *z++;
+    c = *z++;
   }
   --z;
   StpCpy(z, s);
-  return OBJECT(ATOM, SUB((long)z, q->str));
+  return OBJECT(ATOM, z - q->str);
 }
 
-static unsigned char XlatSyntax(unsigned char b) {
-  return PEEK_ARRAY(q, syntax, b, 0);
+static void PrintChar(unsigned char b) {
+  if (write(1, &b, 1) == -1) exit(1);
 }
 
 static void PrintString(char *s) {
   char c;
   for (;;) {
-    if (!(c = PEEK(s, 0, 0))) break;
+    if (!(c = s[0])) break;
     PrintChar(c);
     ++s;
   }
 }
 
 static int GetChar(void) {
-  int c;
-  c = ReadChar();
-#if RETRO
-  if (c >= 'a') {
-    CompilerBarrier();
-    if (c <= 'z') c -= 'a' - 'A';
+  unsigned char b;
+  static char *l, *p;
+  if (l || (l = p = bestlineWithHistory("* ", "sectorlisp"))) {
+    if (*p) {
+      b = *p++;
+    } else {
+      free(l);
+      l = p = 0;
+      b = '\n';
+    }
+    return b;
+  } else {
+    PrintChar('\n');
+    exit(0);
   }
-#endif
-#if DELETE
-  if (c == '\b') return c;
-#endif
-  PrintChar(c);
-  if (c == '\r') PrintChar('\n');
-  return c;
 }
 
 static void GetToken(void) {
   char *t;
-  unsigned char b, x;
+  int b, x;
   b = q->look;
   t = q->token;
   for (;;) {
-    x = XlatSyntax(b);
+    x = q->syntax[b];
     if (x != ' ') break;
     b = GetChar();
   }
   if (x) {
-    STOS(t, b);
+    *t++ = b;
     b = GetChar();
   } else {
     while (b && !x) {
-      if (!DELETE || b != '\b') {
-        STOS(t, b);
-      } else if (t > q->token) {
-        PrintString("\b \b");
-        if (t > q->token) --t;
-      }
+      *t++ = b;
       b = GetChar();
-      x = XlatSyntax(b);
+      x = q->syntax[b];
     }
   }
-  STOS(t, 0);
+  *t++ = 0;
   q->look = b;
 }
 
-static WORD ConsumeObject(void) {
+static int ConsumeObject(void) {
   GetToken();
   return GetObject();
 }
 
-static WORD Cadr(long x) {
-  return Car(Cdr(x));  // ((A B C D) (E F G) H I) → (E F G)
+static int Cadr(int x) {
+  return Car(Cdr(x));  /* ((A B C D) (E F G) H I) → (E F G) */
 }
 
-static WORD List(long x, long y) {
+static int List(int x, int y) {
   return Cons(x, Cons(y, NIL));
 }
 
-static WORD Quote(long x) {
+static int Quote(int x) {
   return List(ATOM_QUOTE, x);
 }
 
-static WORD GetQuote(void) {
+static int GetQuote(void) {
   return Quote(ConsumeObject());
 }
 
-static WORD AddList(WORD x) {
+static int AddList(int x) {
   return Cons(x, GetList());
 }
 
-static WORD GetList(void) {
+static int GetList(void) {
   GetToken();
   switch (*q->token & 0xFF) {
     default:
@@ -257,7 +252,7 @@ static WORD GetList(void) {
   }
 }
 
-static WORD GetObject(void) {
+static int GetObject(void) {
   switch (*q->token & 0xFF) {
     default:
       return Intern(q->token);
@@ -270,21 +265,21 @@ static WORD GetObject(void) {
   }
 }
 
-static WORD ReadObject(void) {
+static int ReadObject(void) {
   q->look = GetChar();
   GetToken();
   return GetObject();
 }
 
-static WORD Read(void) {
+static int Read(void) {
   return ReadObject();
 }
 
-static void PrintAtom(long x) {
+static void PrintAtom(int x) {
   PrintString(q->str + VALUE(x));
 }
 
-static void PrintList(long x) {
+static void PrintList(int x) {
 #if QUOTES
   if (Car(x) == ATOM_QUOTE) {
     PrintChar('\'');
@@ -307,7 +302,7 @@ static void PrintList(long x) {
   PrintChar(')');
 }
 
-static void PrintObject(long x) {
+static void PrintObject(int x) {
   if (ISATOM(x)) {
     PrintAtom(x);
   } else {
@@ -315,7 +310,7 @@ static void PrintObject(long x) {
   }
 }
 
-static void Print(long i) {
+static void Print(int i) {
   PrintObject(i);
   PrintString("\r\n");
 }
@@ -324,55 +319,58 @@ static void Print(long i) {
 │ The LISP Challenge § Bootstrap John McCarthy's Metacircular Evaluator    ─╬─│┼
 ╚────────────────────────────────────────────────────────────────────────────│*/
 
-static WORD Caar(long x) {
-  return Car(Car(x));  // ((A B C D) (E F G) H I) → A
+static int Caar(int x) {
+  return Car(Car(x));  /* ((A B C D) (E F G) H I) → A */
 }
 
-static WORD Cdar(long x) {
-  return Cdr(Car(x));  // ((A B C D) (E F G) H I) → (B C D)
+static int Cdar(int x) {
+  return Cdr(Car(x));  /* ((A B C D) (E F G) H I) → (B C D) */
 }
 
-static WORD Cadar(long x) {
-  return Cadr(Car(x));  // ((A B C D) (E F G) H I) → B
+static int Cadar(int x) {
+  return Cadr(Car(x));  /* ((A B C D) (E F G) H I) → B */
 }
 
-static WORD Caddr(long x) {
-  return Cadr(Cdr(x));  // ((A B C D) (E F G) H I) → H
+static int Caddr(int x) {
+  return Cadr(Cdr(x));  /* ((A B C D) (E F G) H I) → H */
 }
 
-static WORD Caddar(long x) {
-  return Caddr(Car(x));  // ((A B C D) (E F G) H I) → C
+static int Caddar(int x) {
+  return Caddr(Car(x));  /* ((A B C D) (E F G) H I) → C */
 }
 
-static WORD Evcon(long c, long a) {
+static int Evcon(int c, int a) {
   return Eval(Caar(c), a) != NIL ? Eval(Cadar(c), a) : Evcon(Cdr(c), a);
 }
 
-static WORD Assoc(long x, long a) {
-  return a != NIL ? Caar(a) == x ? Cdar(a) : Assoc(x, Cdr(a)) : NIL;
+static int Assoc(int x, int a) {
+  return a ? Caar(a) == x ? Cdar(a) : Assoc(x, Cdr(a)) : NIL;
 }
 
-static WORD Pairlis(WORD x, WORD y, WORD a) {
-  if (x == NIL)
-    return a;
-  WORD di = Cons(Car(x), Car(y));
-  WORD si = Pairlis(Cdr(x), Cdr(y), a);
-  return Cons(di, si); // Tail-Modulo-Cons
+static int Pairlis(int x, int y, int a) {  /* it's zip() basically */
+  int di, si;
+  if (!x) return a;
+  di = Cons(Car(x), Car(y));
+  si = Pairlis(Cdr(x), Cdr(y), a);
+  return Cons(di, si); /* Tail-Modulo-Cons */
 }
 
-static WORD Evlis(WORD m, WORD a) {
-  if (m == NIL)
-    return NIL;
-  WORD di = Eval(Car(m), a);
-  WORD si = Evlis(Cdr(m), a);
+static int Evlis(int m, int a) {
+  int di, si;
+  if (!m) return NIL;
+  di = Eval(Car(m), a);
+  si = Evlis(Cdr(m), a);
   return Cons(di, si);
 }
 
-static WORD Apply(WORD fn, WORD x, WORD a) {
+static int Apply(int fn, int x, int a) {
+  int t1, si, ax;
   if (ISATOM(fn)) {
     switch (fn) {
+#if FUNDEF
     case NIL:
       return UNDEFINED;
+#endif
     case ATOM_CAR:
       return Caar(x);
     case ATOM_CDR:
@@ -387,22 +385,20 @@ static WORD Apply(WORD fn, WORD x, WORD a) {
       return Apply(Eval(fn, a), x, a);
     }
   }
-
   if (Car(fn) == ATOM_LAMBDA) {
-    WORD t1 = Cdr(fn);
-    WORD si = Pairlis(Car(t1), x, a);
-    WORD ax = Cadr(t1);
+    t1 = Cdr(fn);
+    si = Pairlis(Car(t1), x, a);
+    ax = Cadr(t1);
     return Eval(ax, si);
   }
-
   return UNDEFINED;
 }
 
-static WORD Eval(WORD e, WORD a) {
+static int Evaluate(int e, int a) {
+  int ax;
   if (ISATOM(e))
     return Assoc(e, a);
-
-  WORD ax = Car(e);
+  ax = Car(e);
   if (ISATOM(ax)) {
     if (ax == ATOM_QUOTE)
       return Cadr(e);
@@ -411,31 +407,42 @@ static WORD Eval(WORD e, WORD a) {
     if (ax == ATOM_LAMBDA)
       return e;
   }
-
   return Apply(ax, Evlis(Cdr(e), a), a);
 }
 
+static int Eval(int e, int a) {
+  int ax;
+#if TRACE
+  PrintString("> ");
+  PrintObject(e);
+  PrintString("\r\n  ");
+  PrintObject(a);
+  PrintString("\r\n");
+#endif
+  ax = Evaluate(e, a);
+#if TRACE
+  PrintString("< ");
+  PrintObject(ax);
+  PrintString("\r\n");
+#endif
+  return ax;
+}
+
 /*───────────────────────────────────────────────────────────────────────────│─╗
 │ The LISP Challenge § User Interface                                      ─╬─│┼
 ╚────────────────────────────────────────────────────────────────────────────│*/
 
 void Repl(void) {
   for (;;) {
-#if PROMPT
-    PrintString("* ");
-#endif
     Print(Eval(Read(), q->globals));
   }
 }
 
 int main(int argc, char *argv[]) {
-  RawMode();
   SetupSyntax();
   SetupBuiltins();
-#if PROMPT
+  bestlineSetXlatCallback(bestlineUppercase);
   PrintString("THE LISP CHALLENGE V1\r\n"
               "VISIT GITHUB.COM/JART\r\n");
-#endif
   Repl();
-  return 0;
 }

+ 0 - 180
lisp.h

@@ -1,180 +0,0 @@
-#ifndef SECTORLISP_H_
-#define SECTORLISP_H_
-#include <unistd.h>
-#include <termios.h>
-#include <sys/ioctl.h>
-
-/*───────────────────────────────────────────────────────────────────────────│─╗
-│ The LISP Challenge § Richard Stallman Math 55 Systems Integration Code   ─╬─│┼
-╚────────────────────────────────────────────────────────────────────────────│*/
-
-#define CompilerBarrier() asm volatile("" ::: "memory")
-
-#define ISATOM(x) /* a.k.a. !(x&1) */                        \
-  ({                                                         \
-    _Bool IsAtom;                                            \
-    asm("test%z1\t$1,%1" : "=@ccnz"(IsAtom) : "Qm"((char)x)); \
-    IsAtom;                                                  \
-  })
-
-#define OBJECT(t, v) /* a.k.a. v<<1|t */ \
-  ({                                     \
-    __typeof(v) Val = (v);               \
-    asm("shl\t%0" : "+r"(Val));          \
-    Val | (t);                           \
-  })
-
-#define SUB(x, y) /* a.k.a. x-y */           \
-  ({                                         \
-    __typeof(x) Reg = (x);                   \
-    asm("sub\t%1,%0" : "+rm"(Reg) : "g"(y)); \
-    Reg;                                     \
-  })
-
-#define STOS(di, c) asm("stos%z1" : "+D"(di), "=m"(*(di)) : "a"(c))
-#define LODS(si)                                     \
-  ({                                                 \
-    typeof(*(si)) c;                                 \
-    asm("lods%z2" : "+S"(si), "=a"(c) : "m"(*(si))); \
-    c;                                               \
-  })
-
-static inline void *SetMemory(void *di, int al, unsigned long cx) {
-  asm("rep stosb"
-      : "=D"(di), "=c"(cx), "=m"(*(char(*)[cx])di)
-      : "0"(di), "1"(cx), "a"(al));
-  return di;
-}
-
-static inline void *CopyMemory(void *di, const void *si, unsigned long cx) {
-  asm("rep movsb"
-      : "=D"(di), "=S"(si), "=c"(cx), "=m"(*(char(*)[cx])di)
-      : "0"(di), "1"(si), "2"(cx));
-  return di;
-}
-
-static void RawMode(void) {
-#ifndef __REAL_MODE__
-  struct termios t;
-  if (ioctl(1, TCGETS, &t) != -1) {
-    t.c_cc[VMIN] = 1;
-    t.c_cc[VTIME] = 1;
-    t.c_iflag &= ~(INPCK | ISTRIP | PARMRK | INLCR | IGNCR | ICRNL | IXON);
-    t.c_lflag &= ~(IEXTEN | ICANON | ECHO | ECHONL);
-    t.c_cflag &= ~(CSIZE | PARENB);
-    t.c_oflag &= ~OPOST;
-    t.c_cflag |= CS8;
-    t.c_iflag |= IUTF8;
-    ioctl(1, TCSETS, &t);
-  }
-#endif
-}
-
-__attribute__((__noinline__)) static void PrintChar(long c) {
-#ifdef __REAL_MODE__
-  asm volatile("mov\t$0x0E,%%ah\n\t"
-               "int\t$0x10"
-               : /* no outputs */
-               : "a"(c), "b"(7)
-               : "memory");
-#else
-  static short buf;
-  int rc;
-  buf = c;
-  write(1, &buf, 1);
-#endif
-}
-
-static int ReadChar(void) {
-  int c;
-#ifdef __REAL_MODE__
-  asm volatile("int\t$0x16" : "=a"(c) : "0"(0) : "memory");
-  c &= 0xff;
-#else
-  static int buf;
-  read(0, &buf, 1);
-  c = buf;
-#endif
-  return c;
-}
-
-#define PEEK_(REG, BASE, INDEX, DISP)                                    \
-  ({                                                                     \
-    __typeof(*(BASE)) Reg;                                               \
-    if (__builtin_constant_p(INDEX) && !(INDEX)) {                       \
-      asm("mov\t%c2(%1),%0"                                              \
-          : REG(Reg)                                                     \
-          : "bDS"(BASE), "i"((DISP) * sizeof(*(BASE))),                  \
-            "m"(BASE[(INDEX) + (DISP)]));                                \
-    } else {                                                             \
-      asm("mov\t%c3(%1,%2),%0"                                           \
-          : REG(Reg)                                                     \
-          : "b"(BASE), "DS"((long)(INDEX) * sizeof(*(BASE))),            \
-            "i"((DISP) * sizeof(*(BASE))), "m"(BASE[(INDEX) + (DISP)])); \
-    }                                                                    \
-    Reg;                                                                 \
-  })
-
-#define PEEK(BASE, INDEX, DISP) /* a.k.a. b[i] */        \
-  (sizeof(*(BASE)) == 1 ? PEEK_("=Q", BASE, INDEX, DISP) \
-                        : PEEK_("=r", BASE, INDEX, DISP))
-
-#define PEEK_ARRAY_(REG, OBJECT, MEMBER, INDEX, DISP)                     \
-  ({                                                                      \
-    __typeof(*(OBJECT->MEMBER)) Reg;                                      \
-    if (!(OBJECT)) {                                                      \
-      asm("mov\t%c2(%1),%0"                                               \
-          : REG(Reg)                                                      \
-          : "bDS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))),             \
-            "i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) +         \
-                sizeof(*(OBJECT->MEMBER)) * (DISP)),                      \
-            "m"(OBJECT->MEMBER));                                         \
-    } else {                                                              \
-      asm("mov\t%c3(%1,%2),%0"                                            \
-          : REG(Reg)                                                      \
-          : "b"(OBJECT), "DS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))), \
-            "i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) +         \
-                sizeof(*(OBJECT->MEMBER)) * (DISP)),                      \
-            "m"(OBJECT->MEMBER));                                         \
-    }                                                                     \
-    Reg;                                                                  \
-  })
-
-#define PEEK_ARRAY(OBJECT, MEMBER, INDEX, DISP) /* o->m[i] */ \
-  (sizeof(*(OBJECT->MEMBER)) == 1                             \
-       ? PEEK_ARRAY_("=Q", OBJECT, MEMBER, INDEX, DISP)       \
-       : PEEK_ARRAY_("=r", OBJECT, MEMBER, INDEX, DISP))
-
-#define POKE_ARRAY_(REG, OBJECT, MEMBER, INDEX, DISP, VALUE)        \
-  do {                                                              \
-    if (!(OBJECT)) {                                                \
-      asm("mov\t%1,%c3(%2)"                                         \
-          : "=m"(OBJECT->MEMBER)                                    \
-          : REG((__typeof(*(OBJECT->MEMBER)))(VALUE)),              \
-            "bDS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))),       \
-            "i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) +   \
-                sizeof(*(OBJECT->MEMBER)) * (DISP)));               \
-    } else {                                                        \
-      asm("mov\t%1,%c4(%2,%3)"                                      \
-          : "=m"(OBJECT->MEMBER)                                    \
-          : REG((__typeof(*(OBJECT->MEMBER)))(VALUE)), "b"(OBJECT), \
-            "DS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))),        \
-            "i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) +   \
-                sizeof(*(OBJECT->MEMBER)) * (DISP)));               \
-    }                                                               \
-  } while (0)
-
-#define POKE_ARRAY(OBJECT, MEMBER, INDEX, DISP, VALUE) /* o->m[i]=v */ \
-  do {                                                                 \
-    __typeof(*(OBJECT->MEMBER)) Reg;                                   \
-    switch (sizeof(*(OBJECT->MEMBER))) {                               \
-      case 1:                                                          \
-        POKE_ARRAY_("Q", OBJECT, MEMBER, INDEX, DISP, VALUE);          \
-        break;                                                         \
-      default:                                                         \
-        POKE_ARRAY_("r", OBJECT, MEMBER, INDEX, DISP, VALUE);          \
-        break;                                                         \
-    }                                                                  \
-  } while (0)
-
-#endif /* SECTORLISP_H_ */

+ 0 - 36
lisp.lds

@@ -1,36 +0,0 @@
-ENTRY(_start)
-
-SECTIONS {
-
-  .text 0x7c00 - 0x600 : {
-    *(.start)
-    *(.text.startup)
-    rodata = .;
-    *(.rodata .rodata.*)
-    . = 0x1fe;
-    SHORT(0xaa55);
-    *(.text .text.*)
-    _etext = .;
-    . = ALIGN(512);
-  }
-
-  .bss : {
-    bss = .;
-    *(.bss .bss.*)
-    *(COMMON)
-  }
-
-  /DISCARD/ : {
-    *(.yoink)
-    *(.*)
-  }
-}
-
-boot        = 0x7c00;
-q.syntax    = 8192*2;
-q.look      = 8192*2+256;
-q.globals   = 8192*2+256+2;
-q.index     = 8192*2+256+2+2;
-q.token     = 8192*2+256+2+2+2;
-q.str       = 8192*2+256+2+2+2+128;
-v_sectors   = SIZEOF(.text) / 512;

+ 3 - 2
lisp.lisp

@@ -23,8 +23,9 @@
 ;;
 ;; Listed Projects
 ;;
-;; - 836 bytes: https://github.com/jart/sectorlisp
+;; - 512 bytes: https://github.com/jart/sectorlisp
 ;; - 13 kilobytes: https://t3x.org/klisp/
+;; - 47 kilobytes: https://github.com/matp/tiny-lisp
 ;; - 150 kilobytes: https://github.com/JeffBezanson/femtolisp
 ;; - Send pull request to be listed here
 ;;
@@ -72,6 +73,7 @@ NIL
 ;; CORRECT RESULT OF EXPRESSION IS STILL `A`
 ;; REQUIRES CONS CAR CDR QUOTE ATOM EQ LAMBDA COND
 ;; SIMPLIFIED BUG FIXED VERSION OF JOHN MCCARTHY PAPER
+;; NOTE: ((EQ (CAR E) NIL) (QUOTE *UNDEFINED)) CAN HELP
 ((LAMBDA (ASSOC EVCON BIND APPEND EVAL)
    (EVAL (QUOTE ((LAMBDA (FF X) (FF X))
                  (QUOTE (LAMBDA (X)
@@ -98,7 +100,6 @@ NIL
             ((ATOM E) (ASSOC E A))
             ((ATOM (CAR E))
              (COND
-               ((EQ (CAR E) NIL) (QUOTE *UNDEFINED))
                ((EQ (CAR E) (QUOTE QUOTE)) (CAR (CDR E)))
                ((EQ (CAR E) (QUOTE ATOM)) (ATOM (EVAL (CAR (CDR E)) A)))
                ((EQ (CAR E) (QUOTE EQ)) (EQ (EVAL (CAR (CDR E)) A)

+ 0 - 177
realify.sed

@@ -1,177 +0,0 @@
-#-*-mode:sed;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐
-#───vi: et ft=sed ts=8 tw=8 fenc=utf-8 :vi─────────────────┘
-#
-# SYNOPSIS
-#
-#   sed -i -f realify.sed foo.s
-#
-# OVERVIEW
-#
-#   This converts ints and longs to shorts while preserving System V ABI
-#   x86_64 compatibility. This works better than gcc -m16 because we can
-#   avoid the ASZ and OSZ prefixes in most cases while also avoiding the
-#   legacy 32-bit calling conventions.
-
-# remove comments
-s/[ \t][ \t]*#.*//
-
-s/leave\(q\|\)/leavew/
-s/call\(q\|\)/callw/
-s/ret\(q\|\)/retw/
-s/popq\t%rbp/pop\t%bp/
-s/pushq\t%rbp/push\t%bp/
-s/pushq\t\(.*\)/sub\t$6,%sp\n\tpush\t\1/
-s/popq\t\(.*\)/pop\t\1\n\tadd\t$6,%sp/
-
-# # preserve hardcoded stack offsets
-# # bloats code size 13%
-# s/leave\(q\|\)/leavew\n\tadd\t$6,%sp/
-# s/call\(q\|\)\t/sub\t$6,%sp\n\tcallw\t/
-# s/ret\(q\|\)/retw\t$6/
-# s/pushq\t\(.*\)/sub\t$6,%sp\n\tpush\t\1/
-# s/popq\t\(.*\)/pop\t\1\n\tadd\t$6,%sp/
-
-s/, /,/g
-
-# 32-bitify
-s/rax/eax/g
-s/rbx/ebx/g
-s/rcx/ecx/g
-s/rdx/edx/g
-s/rbp/ebp/g
-s/rdi/edi/g
-s/rsi/esi/g
-s/rsp/esp/g
-
-# unextension
-s/movswl/mov/
-s/movzwl/mov/
-s/movslq/mov/
-s/movzlq/mov/
-s/movsbl/movsbw/
-
-# unsuffix
-s/^\(\t\(fild\|fist\|fistp\|fiadd\|fisub\|fisubr\|fimul\|fidiv\|fidivr\|ficom\)\)q\t/\1\t/
-s/^\(\t\(mov\|add\|adc\|cmp\|test\|lea\|sbb\|mul\|imul\|div\|idiv\|in\|out\|xor\|sub\|and\|or\|rol\|ror\|rcl\|rcr\|shl\|shr\|sal\|sar\|inc\|dec\|not\|neg\)\)l\t/\1w\t/
-s/^\(\t[a-z]*\)q\t/\1w\t/
-s/movsww/mov/
-
-# remove fluff
-s/mov\t%eax,%eax//
-s/mov\t%ebx,%ebx//
-s/mov\t%ecx,%ecx//
-s/mov\t%edx,%edx//
-s/mov\t%ebp,%ebp//
-s/mov\t%edi,%edi//
-s/mov\t%esi,%esi//
-s/mov\t%esp,%esp//
-
-# make pic absolute
-s/(%rip)//
-
-# legal real mode modrm
-s/(%ebx)/(%bx)/
-s/(%edi)/(%di)/
-s/(%esi)/(%si)/
-s/(%ebp)/(%bp)/
-s/(%ebx,%esi\(,1\|\))/(%bx,%si)/
-s/(%ebx,%edi\(,1\|\))/(%bx,%di)/
-s/(%ebp,%esi\(,1\|\))/(%bp,%si)/
-s/(%ebp,%edi\(,1\|\))/(%bp,%di)/
-
-# we need the asz prefix
-s/(%eax,%eax/(%EAX,%EAX/
-s/(%eax,%ebp/(%EAX,%EBP/
-s/(%eax,%ebx/(%EAX,%EBX/
-s/(%eax,%ecx/(%EAX,%ECX/
-s/(%eax,%edi/(%EAX,%EDI/
-s/(%eax,%edx/(%EAX,%EDX/
-s/(%eax,%esi/(%EAX,%ESI/
-s/(%ebp,%eax/(%EBP,%EAX/
-s/(%ebp,%ebp/(%EBP,%EBP/
-s/(%ebp,%ebx/(%EBP,%EBX/
-s/(%ebp,%ecx/(%EBP,%ECX/
-s/(%ebp,%edi/(%EBP,%EDI/
-s/(%ebp,%edx/(%EBP,%EDX/
-s/(%ebp,%esi/(%EBP,%ESI/
-s/(%ebx,%eax/(%EBX,%EAX/
-s/(%ebx,%ebp/(%EBX,%EBP/
-s/(%ebx,%ebx/(%EBX,%EBX/
-s/(%ebx,%ecx/(%EBX,%ECX/
-s/(%ebx,%edi/(%EBX,%EDI/
-s/(%ebx,%edx/(%EBX,%EDX/
-s/(%ebx,%esi/(%EBX,%ESI/
-s/(%ecx,%eax/(%ECX,%EAX/
-s/(%ecx,%ebp/(%ECX,%EBP/
-s/(%ecx,%ebx/(%ECX,%EBX/
-s/(%ecx,%ecx/(%ECX,%ECX/
-s/(%ecx,%edi/(%ECX,%EDI/
-s/(%ecx,%edx/(%ECX,%EDX/
-s/(%ecx,%esi/(%ECX,%ESI/
-s/(%edi,%eax/(%EDI,%EAX/
-s/(%edi,%ebp/(%EDI,%EBP/
-s/(%edi,%ebx/(%EDI,%EBX/
-s/(%edi,%ecx/(%EDI,%ECX/
-s/(%edi,%edi/(%EDI,%EDI/
-s/(%edi,%edx/(%EDI,%EDX/
-s/(%edi,%esi/(%EDI,%ESI/
-s/(%edx,%eax/(%EDX,%EAX/
-s/(%edx,%ebp/(%EDX,%EBP/
-s/(%edx,%ebx/(%EDX,%EBX/
-s/(%edx,%ecx/(%EDX,%ECX/
-s/(%edx,%edi/(%EDX,%EDI/
-s/(%edx,%edx/(%EDX,%EDX/
-s/(%edx,%esi/(%EDX,%ESI/
-s/(%esi,%eax/(%ESI,%EAX/
-s/(%esi,%ebp/(%ESI,%EBP/
-s/(%esi,%ebx/(%ESI,%EBX/
-s/(%esi,%ecx/(%ESI,%ECX/
-s/(%esi,%edi/(%ESI,%EDI/
-s/(%esi,%edx/(%ESI,%EDX/
-s/(%esi,%esi/(%ESI,%ESI/
-s/(%esp,%eax/(%ESP,%EAX/
-s/(%esp,%ebp/(%ESP,%EBP/
-s/(%esp,%ebx/(%ESP,%EBX/
-s/(%esp,%ecx/(%ESP,%ECX/
-s/(%esp,%edi/(%ESP,%EDI/
-s/(%esp,%edx/(%ESP,%EDX/
-s/(%esp,%esi/(%ESP,%ESI/
-s/(,%eax/(,%EAX/
-s/(,%ebx/(,%EBX/
-s/(,%ecx/(,%ECX/
-s/(,%edx/(,%EDX/
-s/(,%esi/(,%ESI/
-s/(,%edi/(,%EDI/
-s/(,%ebp/(,%EBP/
-s/(%eax)/(%EAX)/
-s/(%ecx)/(%ECX)/
-s/(%edx)/(%EDX)/
-s/(%esp)/(%ESP)/
-
-# 16bitify
-s/eax/ax/g
-s/ebx/bx/g
-s/ecx/cx/g
-s/edx/dx/g
-s/ebp/bp/g
-s/edi/di/g
-s/esi/si/g
-s/esp/sp/g
-
-# sigh :\
-# gcc needs a flag for not using rex byte regs. workaround:
-# - %dil can be avoided through copious use of STOS() macro
-# - %sil can be avoided through copious use of LODS() macro
-# - %bpl shouldn't be allocated due to -fno-omit-frame-pointer
-# - %spl shouldn't be allocated like ever
-# beyond that there's only a few cases where %dil and %sil
-# need some handcoded asm() macros to workaround, for example
-# if ARG1 is long and you say (ARG1 & 1) gcc will use %dil
-# so just kludge it using asm("and\t$1,%0" : "+Q"(ARG1))
-#s/dil/bl/g
-#s/sil/bh/g
-#s/spl/bl/g
-#s/bpl/bh/g
-
-# nope
-s/cltq//

+ 0 - 23
realify.sh

@@ -1,23 +0,0 @@
-#!/bin/sh
-#
-# SYNOPSIS
-#
-#   gcc -g0 -Os -wrapper realify.sh -ffixed-r{8,9,1{0,1,2,4,5}}
-#
-# OVERVIEW
-#
-#   Reconfigures x86_64 compiler to emit 16-bit PC boot code.
-
-if [ "${1##*/}" = as ]; then
-  for x; do
-    if [ "${x##*.}" = s ]; then
-      {
-        printf "\t.code16gcc"
-        sed -f realify.sed "$x"
-      } >"$x".tmp
-      mv -f "$x".tmp "$x"
-    fi
-  done
-fi
-
-exec "$@"

+ 0 - 46
start.S

@@ -1,46 +0,0 @@
-/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8     -*-│
-│vi: set et ft=asm ts=8 tw=8 fenc=utf-8                                     :vi│
-╞══════════════════════════════════════════════════════════════════════════════╡
-│ Copyright 2020 Justine Alexandra Roberts Tunney                              │
-│                                                                              │
-│ Permission to use, copy, modify, and/or distribute this software for         │
-│ any purpose with or without fee is hereby granted, provided that the         │
-│ above copyright notice and this permission notice appear in all copies.      │
-│                                                                              │
-│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
-│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
-│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
-│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
-│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
-│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
-│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
-│ PERFORMANCE OF THIS SOFTWARE.                                                │
-╚─────────────────────────────────────────────────────────────────────────────*/
-.section .start,"ax",@progbits
-.globl	_start
-.code16
-
-_start:	jmp	1f				# some bios scan for short jump
-1:	ljmp	$0x600>>4,$_begin		# end of bios data roundup page
-
-_begin:	push	%cs				# memory model cs=ds=es = 0x600
-	push	%cs
-	push	%cs
-	pop	%ds
-	pop	%es
-	pop	%ss
-	mov	$0x70000>>4,%sp
-	cld
-	xor	%ax,%ax
-	xor	%di,%di
-	mov	$0x7c00-0x600,%cx
-	rep stosb				# clears our bss memory
-	xchg	%di,%bx				# start buffer at 07c00
-	inc	%cx				# start at first sector
-	xor	%dh,%dh				# drive dl head zero
-	mov	$0x0200+v_sectors,%ax		# read sectors
-	int	$0x13				# disk service
-//	𝑠𝑙𝑖𝑑𝑒
-
-	.section .yoink
-	nopw	main