From 4233210a863dde3619426608c5eb7e81addbef4c Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Fri, 8 Oct 2021 21:11:00 -0700 Subject: [PATCH] Remove old code and update documentation --- LICENSE | 1 + Makefile | 60 +- README.md | 80 +- bestline.c | 3481 ++++++++++++++++++++++++++++++++++++++++++++ bestline.h | 33 + bin/footprint.png | Bin 11422 -> 11957 bytes bin/lisp.elf.linux | Bin 9256 -> 0 bytes lisp.c | 335 ++--- lisp.h | 180 --- lisp.lds | 36 - lisp.lisp | 5 +- realify.sed | 177 --- realify.sh | 23 - start.S | 46 - 14 files changed, 3755 insertions(+), 702 deletions(-) create mode 100644 bestline.c create mode 100644 bestline.h delete mode 100755 bin/lisp.elf.linux delete mode 100644 lisp.h delete mode 100644 lisp.lds delete mode 100644 realify.sed delete mode 100755 realify.sh delete mode 100644 start.S diff --git a/LICENSE b/LICENSE index ca40314..b5f7997 100644 --- a/LICENSE +++ b/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 diff --git a/Makefile b/Makefile index 51e1f6a..b1d618a 100644 --- a/Makefile +++ b/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 $< $@ + objcopy -S -O binary sectorlisp.bin.dbg sectorlisp.bin diff --git a/README.md b/README.md index acbbc72..3ab2784 100644 --- a/README.md +++ b/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.

- Binary Footprint Comparison + Binary Footprint Comparison

-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 -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! +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 +``` + +Further information may be found on [our wiki](https://github.com/jart/sectorlisp/wiki). ## Demo diff --git a/bestline.c b/bestline.c new file mode 100644 index 0000000..b49e598 --- /dev/null +++ b/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 │ +│ Copyright 2010-2016 Salvatore Sanfilippo │ +│ Copyright 2010-2013 Pieter Noordhuis │ +│ │ +│ 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 \\n\ +Copyright 2010-2016 Salvatore Sanfilippo \\n\ +Copyright 2010-2013 Pieter Noordhuis \""); + +#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 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 . 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; +} diff --git a/bestline.h b/bestline.h new file mode 100644 index 0000000..6e03b28 --- /dev/null +++ b/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)); diff --git a/bin/footprint.png b/bin/footprint.png index 60b3bd70ec846fdbd5a1c1306c366a95fb7d2b99..0232c17c470dc52e8a4368468a4dcdb38b1fd701 100644 GIT binary patch literal 11957 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 I)ZkvAw6Q1G|R1jgy0cgN=Ovc)&p( z50B8>#mLOZOh;SR&eL7U<`0aJzdHzrjfW?%uG@lfDIBicCGUF;P& zO(k@MbwJ7v&Mr>_y&Mb!b&c!--RxxSIh7RI<^5%G1l%2bY}ozXUwU}U`YUk$MOPO0 z{AU=z$^I9JkDCJL-%Oe5JY!e(^m1UA5Rw$M6A_nYmy!_@krWpelN4YV6&8^J2ulM* z!~}&!WQ8SVC8XH@_2b0N=4J0FYoMa`ueoqN1x{xlACN2n0D(Y+AYwwEUQPfJ85tZ6 zQGlqZAPz#%JHW%o#$V9Go9iD6Dh}RuUM?UX7f%oNKNM|jJ$-!?IB}f*{R!?M9i4v@ z_VE6fpl~7s_}hR0B0|CdclSTv^%u0akAcJgu<>7^y^R7u4gdoOZ%#n_Ya-_hA*n7swShRtSTZcBOxN9`sA^wxQeoxsDz4) zq~v2|De-@BH9Wk1Y&`57{+ZhaH}}7ARsL68S!FK=8y`FP?+1U@=cZUgM9T|7mKErO z9(#CQcGlXdneG)C+EVyzI@*&ckQSM<7a)F9llP<2k_UY(a8*&IBU!8RqqoSxND=9n zzfB{RqQreQSJ~cp3+o)xuqzsf&QIxp2RvsI$p9rr=%-t{zr*$D|89+tqoRL#&@VDBJg2DEdo^8M`DnI z<$-Ta`4wFshReDLwN@l=R_%vzD}9gyx0yI^Se87(e5J9lMa7(*HhxHo9{*PL_Tofz z+U$_<+z{#cJf(d-#l7jnkF z@z%9RAuT*F^+c|Y+Qi5&NbvOzKeD{`dqlO!^W>llpVVAQwask-p6_;cOJEP{6}NEK zSj0PEbg+4w@&J2bc6xODg-@;|Pg=NY=~0vR4UFa8U|`#8zv023b#%~*Yz8N}tf1t? zk(G(K_K3P4@zj}R<+u2^`#-Edb$AXfQ`7+53rhH|3}*XmFiyyB-bb3*zSB)B8@L3vYXs++#1t5(`Ii6^rp#WGr#_sxD9y*NrD z0j`n5l}ESB7$W{NK1+4 z^T6X+qER^6^B=D*lWO37IN6bEErGUE4VDzph!bO%xC9|N-9%Ax69&x|ba|1V3H58a0d}yLiLiK0#@5`*`Q>5zmjWFo zTMdXZ-CN)I>GesMdAaw^A_jK_sz9flHy!-QCRysE64SpYvr@&I8JgmGtfmzIynfgE-9nA~< z)`IA7-8Nd&EMBZ;$;Z;0&FM)L)b$fI<7i@k-37x_kEm{S2ed<)GD zO+#fZ-96)+J5WR=Z!EGxGJqK2qhLD2lbo#=bJs)=vBZxn=6)9a*Q^Z$c-sihEWL%| zQt)Us1A2m^phKY{r8#3^!y3qU`&aH4ai)3S|4xU?{LF@C*EVR$QEcX(4fvu8Y%tF68K^2_{GxK z^6{{My#Y5-@DG|1o8#tZ$u?smc_`_z9!NV3jD04!83AJJm8J;zP-XOLx#m`WcjD$! zr-5j3ZR)oX_?~~Ua#_BCq~5vkz{e6$96xy7$L`twJ+v+D!E%0r(CFd4fu>Q(cWF!6 znivazO60ck)7INBf>@BA7jz;7{*);0wdj|jA>F|0KLVN*__4h>APWGhl<>hcfF`;r^QcjiwwFf_d3gNPr55$>$L-$i*w${mZ2 zkT0rz5Mov6=f1i8OVAa;f~IH(lvt=Ez7qeaX0kcdYG(5A3Z*^uU#0ku+xzeJ%xk4YliXpKU+Cp{c> zri^WvD9lJ{UJ1x6#)tOHjK~q=K!`C1_bDoa{N=L10HT5I32n*!(IPu~6>N&ZzFYJsMNd*0@=0Ja~{K}!mu zOIJ?tsF&@VR_iXkL%ztKk%Q6}hK8|L@d7;%{hW~3?ZxyBa}mn3z$J=HQjDyhmxp}S zO6vhkyuZn6Op=+El>}mKYNIxXgrVXrAxl@c;Dcnlg4R7iDFjfvaXU-o^9>B@8_Kvf zGpOWdc>5)71ysIj$eR{2xisjpOYaG{{-t+uio|f_tTKYXR$zuAaa) zQx37ED-3+JL+QW_sX(oI!Hij6nVh^L3#kf-XurGx0=$|bS*dK|8CZ(Ou6r(cCgwu2 z{0WfO_!zSMZUlJhkm~%57}ZmV*)828LgtgK1l!0?w7?N|OPdz(U0N@!qcQdsc6UjL zH5}R?n(NRO2W5E9m`e5wxB7gji%@u0#>sM^{l=`+;wXy|Ms46-`=tVyEsK0m4us*p z__TIHHKG)U1=~s%yI#|wmcj6}7vH2Wi7;MRmrK9(+%hSc0ODZCAtDr;90ZwH3qW-M z>8Gv@I2A!rR`*4@a4rQUYJ%=71~;DoYLB@c3*J=ff$7+iQG=pbW1%4o%jru~rvg#I^z4CCif77JNc?1ogxQX;RJAxf;F z7x*Y{Wmra*!j#N!MU=PGBpzgo13qyauY0^)csOQBUT;sxtobomWXNc!k>=&$(11Sx zg&MBudWBtFY0b-Vr;z{JL<<3|QW|zk?k?a%whrKPEfc@EkR`B_{_ofA5K|oM;;0i@ zQ_6N&pPVV=2InF`ZZ8dN+LfRa!PrB%;MF(DzwDt7gXglXD2iB2x;2qOXcqnVv~Vami_v3#EK-$Ru5x7$>q8vRyKxY;yhOsW?Suhv%>-*o*cLzH9>YRN zk2n`IThArj4?V390}+7oj^2<126S!No`|9DV>u(=p~Ldu5^A~)P@G6kG3*qi)Q4_+ zn+<8Owuv8_PEAPObxRK}`sFuclhCIy^m?iUp7&6}pzobVu~lMx@He_r=FD`7Nzrme zDJ`u*%&9uO1WW2x3_Ae%x9xq2TCL;+0MCZFGn^Nw2Wm|1j~YjcLr6cCfk$`SNUSzu zs(TJ*P?@*Jn*&XDlp=Q2#yOGzBmRnH!|f2)6@!fl7mooE6jmtBMmx=jJe7w1d^i4K z-U1^gD;vXqbwOo*pdeZzd&c~PH$312hdbb~TtL&kVN}GAC4lbXhQtc59CWe-j_|st zCuM9sZ)?W}Y`Wx$D1BmK}rzco^NkWR>xA2dhITWSY0B@Ffq9x^>J$M^e(soIC`HCdj;A}lJ9Bx_8O z;}%k0M#+B7aFx4N@D=tX_ln6eN`V=osLQ3aQmJmSI&#{IU`tL&@^~+lYGr@*u4<{$ z{=4XpOVb^K*r#Hs2@Y5bVE)OmcDg4GZ7N;^A<9&u)y8kS3!Gs2YkH-!VE>F3IdDUB z!xUcAvDTssrvG}6HSfwejx=WBejc}rTw!+N)uJa{uXmV-pI!W@9w3H?af8?1OUfEy zM3%mOP`RwTr#2XHsA*I)@exQ_KVXyvoOyU@@hfuR9d7j-dudTOA1=R-uS_-tvB3*-UE@;=M+9pr0~y=yk9uz%6T@R+ zGo>$)$&_ePAFHxd&;S=GY5^ak5qIKa)oQe!AD||GDs$(p-qmd*8Qqc<+_R<1B0 z!y#m{NG!aI)zR$)q@Qb5#IO0e#BNUYZd;S&(p4#Z*ZngD(%IGI<2qDM^HqnfF30W4 z+iW(iE5dE=bW12_31mLVS-D%C3fr80g>_n~M0%L-E87ON%~iA|KwE13LIT}SSW#K< zsXkX6du_innS`D%014k-385}i_9DF8yF3z5xN}oWoDi}UTo9qwSb5rd=WyV4m&hiUmuxWK%Y!+@>+-?T zudGj`Ewg~KHw5ZX+P-1lNIUZ)+ruMe=SQZraO;lwN!FN#{kuPbw?Eu+qC3?5;)#yu zhe~Iof#+TX!4~5BdoBDddOv?Dv)_fI&r6|CwRaWfavlqPU4T(zzAR3#fZ58OhQY+M zIA`ce;~)mkrM%YwZu4VQb$hrdm~;6csNvpwhBbekKg(&M}~1vd=8-l7Go zU!lvF7Nk&h3rG1(&6fjB@2rN%os7`T$ENisxOBSvUS=&BnXF0-GkzxnBjyN+!_DnEDC-EXrjOg6eoNN9Ob_n z()u0-9|tkok%Jq$x(nA@y23*ya-JqEUGal44Sg(u=EyFJU&^Ll^19MDQ0sFlJzJX&-j%9e}o`^+UKeH045j6Ue zaWT_1fbfALtC+8RkN|5Y(6tJ3^NFYlv8RpV!XGYPZE=dvI@Y#*2JyS+N)zl~26UT-9Lb`Z8vt+SSeC z;QS13-HhWuAFk&JTbUCZ{fQVRX?rzjB0fis`NIj*)g;WwQZ_}ZMG>=p3(wi<_Q;Q3e1J1I46yzEo zHE@PKf10LFldNCN<8z$stJDc&6a-yMBlA)?H*yLmU%!7B)E}AE#GBdIZkX4LNh*85 zq!^A=2Eg5{eokrjww7>jg@4A{MF1Dq<%(l-O7CiJDD${`QvxIaayzHyvKega&A)PCA2Pvjn(Oq--l&!eT7#WtK)mEP5&{c= zfIT_(1EuD9>%DfzJjRS4P#vUY9-NB{A$}TyoV}ovHP;y{Y zQmJ)=VeEx!hx|XJblj(fOUK{SwzG^3>5L4eq2khJ9N0rRt9kxPi*delr-MdeX}q49 z-_HidAxc3`;0m4VAksAX%LWMxP|0PXSzM#J^6VY>$MQf6)05p1H?JJ!^Ynm9Z^Y@J z44DS77bx>>lO2f-Jc-G74cmj4$6_~?;X1q%BuG_}wZU>_K9Q#}8R z4SXXKbIU@a$(wLqQ@al&PnyjA*S}PRroV-8UxjS)5qCW)oV7Ht>5SbdcCed1$&wm{?KUVWkbnmqB5_vHh)2vT5o!O%**Gx%*{ zXGo4osISBso9?sZ$;?hnRF6Jn2vtbYEsygUkcsHYg6DPe5S!}dgm;XQ9U zGKIN_*`8S;z}Sa7*0%&U-bkCqcD_%Dar%wQ-4CgGlJ-!kGNReo%2x6W1!XnAttu5*}kp9CC_S zpY-eq6q3Yu!>b?86ehHclCs#{%x0h`Nd%!!nGb`jdnfPn+PIT$(-mkFxqeDcbWF%z zRWy63UvK0;;SdBdr+fY$d1rmi^XG%`1xARb6Iu+#VSldriFiM!=$Z$>Z9JvX5LxaP z64NnO8ESfJ_Vf<0z^@lL5d3A88@Vq{lv@Gu5hzJ^d63+J=Cq64P+nI)|0MUIod6UP z9;Dq0?Nu;!pi7+k`a?alO1$(P8Ep)o@;bu%fjg&xy5Z_bNxy#t7A{YqyP%!U?^7|F zoWIsT74-{`0>!Rg8^VMs(LsYMrNtD^_SO)Ct{yIF2f8ELrp4Pu)>AjQ`Leh}U1fI5 zdiYP_B{_qZ_x;R5!wh8|OJ(b{J=d3sASC{6{B{5n0QOhEc6Dinw`K!3pjp>yvoW`1 z@92l@Z2AQxW$;_s+R&OQ#zD*i$=mB4xlRF5zWu$yrZ>pGV}78L5S8$*#pn$!99pm9 zXta_PT+f7(b&WAF=nc3>9ioUm8~ofp7>)o66m=?$PQF+>w)ArBw<%Fx7Y#_DMHaIdPgrt3_Jch2pg`ZRVrCIZ zO*kfk(AN)oTQ};)eOg2NkSzF;%j)g;nLN#TY2fL75;mRiQ}2iaxQG2s5^?n2?AGg9 z6q{HwXRc8W2`U0pt^H*r@ll>)DfrV6(V*n>lX1E}m8NggH|6wl6+7lZGh>ND;~exR z$y*aMHF{m!jKpqOgOx`HEg9?YMsj~l!-BhAO}jCRWpKzbVe z*`{MCK1Rupxy7%ZduinT!Xt|%zPa7Ot%WBick z>Qx1e$kqqF%8B0(;mU2?6L8e#c>Lf!e74mo4~N0hj8*-^1>9CIEV{`+ z37u|WetlWqmQ5bRfvJ`AWRp#ATC&DAm_^N`PdBrJ;}C(?*L}b~t|8 zOg%UltH1;Sk=SykmO zNRM!Pp<=(W4@O&LmcOAPb1%PL?=^Mg;9Icn<-bmZ*nppU@bRW+kn!FQ3dIJ(khxUu zw$QVv8+%!E5%?IXzWLE~|DEH3jr>u1N|lP-nDX1dV&ohY%XKBYeZA+-0z_!G_7Y8> zNKBR5=Br9Vj1gL%`DUJ_-hk)K(HmLnN*MN?2l~4$?5bAWB20DQPiem+3+Iv4x3Cw(am_Q{ z3-T73sM6p%oEvOSg|YD*iS1ZJ^bZYq1|?wq;4gs$T(i74l{#Q%!6=~$iMKX*siMtaD-JJuY#DLHieEF<0mc1|{+@Tr( z*TXE0pkZWM%#H&+zT9%7~M;UcSR3XDXA z0LVbP5B7V;;+L+KrWG-6b__0+Rrxb`EuxyI}lgeu6O{4IbSaLdG! z{s{+^Iu*E_Cy&YCRRbq(c6PCdh0P-zH&&vsDWjykxH49+=eaRhq#z2gf{prE_PuI5 zkQI}^HO?XnQ?BjopDXUhOUacPbaqt^kG+LHbh+2Gn6vAFI(ABpfte>3HNJpo$W zd_=LYA16kMTr_AN<}^yOjlun{eyZPa)5IXd^j`-QurP?eVzqZdZN8iK zmB^GwGcCr)-!(Lo1f}PQKWy_eBW9RkP+}gqAoGBd47l+w0F#l zBXmuFvqK7E0x(ICvjdMh8ik1SUuBUaoeJjD8*x*!k1W>R2lG>lGDs_2(Ck`Re;>m3MnUzYXXSZq1S$MAl z7e<6|vC%DyoU9gQOx!{)ni{A0LIf=9j30aQbj)X}w0F7${BCl|bUy|mA>Kt5(k9}c zzO^-=nc!`;&2-rbw$IWUWDn+-W_qjca6w99FV9E7LUn<&OuOSn-ebVBQ;|Y`=*C$n zc2i#5gL&b)f0X#IP!bs`2O>e?6zxx3`FDp3bPdG?k;F(PQV1z-e2Xo4Gy--YgNN&N z1w<*{frLBaia-7{gYiFi_~#8^wu^+tCrXN_)n_KJZ!TRqBYkB7I@N5NU|o(8KI(Oo z?@!hiBgZm)Itnt{QJ;hp?#j27sa&t``2i}Cwz!&*KPZjq_h^Yu8dI0L*UbeJ8<(DX za!DC^g-IRkGetZKIB8_SmA{?n&%6(LFZILE` z$W)m%G~u_HwtVT~^9I^P|K4jy9nQb~s7HP2D)xb`CAmHF_GVLOPF#(U?BtRainKE) zffnH?an#!4)|@N~l})Ne%BLhua1bwSrsR}HrY!S(XA%8%L9RhbeeWs!{t(jbec;xAG^bP zEXmsj`E7n}85zsW_A{HlO+8XMI%MaQuYD_fdE`x)jSulL&YLja-xOMAg~VuEbt~*U z@O!uV@10d1cv)taUP8S34dpCJJA_tR7o>aC$Mx~amaew?-}XD!<7!rzlGTwj&B_x@ zf^SdP%|(jiZ00(W6YPp-7Glky{5%R2k+&cz&DYJnJ@+2{wL(3+pTL;0+dr+^fqm~} zGs=1t%!3GE@smFKyY4o4T#fly_UXs{9QX^hL4ba%1jQ2_h6L9Rpo-0>+N_o!Qn(T$ zM6~B9b3kkCd}O4fodIL{C~Vlx0)Ec{0&+}ZS}m?8P!hav+B3hY{3%fB#G^DO7`4A4 zdi6TaFP#nvCnRY*MsE(CKk zZmvmQf==uU_#V7TG@!@Ax!1i0A4FHKrW-oL_tzzNMmbCB@TnVP9SGjciN2W44$S)~ zY*n#pz|4Ws>%62l><(kOiP60;etTKh*>R_VJ@D=8{ymE1nF^?F%?o)QuOS(tU?^v_ zj|DC?AS0RuXnRFrw^fQ5AcROIGR&g<=jTawnhW0^ojnXktmF0`$;ahul_oMG480Nz zL#PebxFec($K9ZC1WkUY!u>(?k#Jf6^@a}aN?*+bw}*rzZQS>B=U)Fzlxdilol))Q znEhVmIQZ-F&{-Hl`en9eK`1*CSdo)4y`6n-rHe9;LwtD2gxf{(V&n!JMc>n{9(8k? zuL@Og1#bqhY{L0jv4e0*{i&a@he9J>hl~)RWkeSwWWkF#R&2tdWZv8=J44?D&ScGV z60!H|y}%b@Ov`~MIPcyZ#Q01VDFafs-v|ipm#9{VdN%z|wlLhWVVH)SoGkR=Jb6r_ z(Jn&IVO;$+VmMTnfC;V)c#VC*V+_g*bKfi*fnz<&cwwMiCr9ZFE-`Ux3r`3sICr+t z{MAMDU)SoZvg%Gee;fc6WXoIIY~C^M+7Vk*Q_gfS+HPx)E_ba%RpTvL;wN;$q)k@v z`=$aH%#Q`WB(232R%qa4x4z-`Jlni&m&B2sbiLbeD1bP}#c0>#Wub_UtpWFMTXN&a z%?ucMhmSFcKGELU8Oz#_3Dt~VK%Qu4Eo$lKNaeiEU{aYRU+Z%Mhtuf0;~hg-k-{=R_yTY$KUf-u0Hh-x!->L|JL&TThss1 z_&@)vc2QTXf2Lsz|EJ*ZzZY@gDgpoV0^;v_ujAeG5PV`h4bSd|>90{@e=3GGRCQG< IpS%eBKPjS)z5oCK diff --git a/bin/lisp.elf.linux b/bin/lisp.elf.linux deleted file mode 100755 index 0459c4e9429224da907debdf3731274cf0c16957..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9256 zcmcIq4R}=5nLaaN$QTnRN_4aMcku}jL<2FF1(od#$(1uYKp=^tz{&pNriyywL`;PyBSh}L-??*t2*kFJ z-ODrg{Jh`yo$q{q_udm8kRvyGJsxIVUiOcSM(?wrm@@B-m5vg_?A&DL1)k5&6|}jS z-JXTFXZftwFPT6?quFMMZ1zXdrNhmszvy0=rK;9Tpq@VM?--k#YhGDCe7r%RCkm(ht;>Ntt{0?9DWsG?iB$GBlQC)i%1Pd~(s-FYO z1>M&}1}Ra))%s4Z#tv|`ZZFs5yRjFV3hg|i$(>xAfnVJLljIGqz76!) zQ(OhZ7p4q=D)>P>Z$Xk|!#~waxi)NcDcMPtGvx{1B5%hE?Vei3;uL5?P5syBGp1yW z=p7w$%F`itcrCihq|tJY&HG1F2SwxqtjO+hg6oheQRi_6QNCIWA7OCIj`JupmJH6WqqkNJ@CX$d!&9wMHF2+SA6^5S{0wf-2! zPEAmH3K7;nzn#?VLv&=uEDkcL+Xv1uuoIMTsPC6zd3u8w^`$@9f(aJ=9zriegfJos zi4}SoeQuU+c|(1lKl=AWkfler(Mo)ZoLN6Iz6e!9KMeCblRkJTX<3aP1&Pq8 zk41+IHGWL;+p#YSBItoodq?ff=7G=;`IflNE|>an2-MF*6}6uXeyiAu7$AcO;tA@{ zLf3OL9m23h1c+CssE_>Z{SUR@#rgtxutf zvM&$lc=j{M4#edFA$p}PS`S&K3cSby?TNsP2_;h&9gW)qc}iw%^a>?2E*j9sLP9H) zdJ^K(7mEj6x=8;Mn9~LNJ(wi$A$x6Me3!(r(TkN#X*6Iu1*FeIgLJ-rFNyQ%lWazP z=~spUJI|eqEk2FD{woK2*Y2VS*$*}<%f5#~aGTGpo+V%mvuCrPmIo-ZL^$y^Z| z8>c<#zsrbBMafI)ldv7jj!y#Y{wZ#8deH0t;UaqwA}q4|0j6sxmA85Vk0ww@5nqPV z>uF3xOX3o-u5+`Q`GK*tUw&kU<3$;NBSrk52N=GXw$fdR_y`%m|IgBmG z`~E1f!=Wktkuzy_f+l^rMerl6J7`OrZKH*1X;WsD(xz0rFNlq85b8nxT$A1226x1D za`iB&@Ed!h5RlSO{>fet6ymWv0UA{W;`E3Ww&(;&YnA#jsPDDSvIae27O1dW!uN$! zF73js)c3L|r8V4{(x;;iWw!=fh?NSdOu;^sV1g!$iS!t2w8_*>RP!uHE40t1iWx|! zyE&`z_YsXGw;0dD>^8)Dw}ZPo>=n-@v9+&`XLW*px0y_V#^>=cviANS2wEZOnx;gc z`DOr0W^Uk-X`*vQVDl8}=&0_K0uNt}zM8;C&7QlF_(oToA8FnFE@mxZm!JxDza}P5 zQ!9;?g=L_{n@EE0XU?R_m^AS0X{_6AR^i+@ao^=ya=NuaC(>_#jk~MPTw$3lDy4sP z3^dZ2G?eC+raeS6fISXUX(nhyCY`2eE@sVy`W95`-Yxjd_<9n8T&cGL!)%-&LWSS{ z9V(m5nKQu#h)UmM+4v9mG$ii!#k2wpx(yQ)3)L3IwWbI=M^0ur#5T+vdBohNO;Tc2 zCKEo7R`dz~4p|dW!bHvs{cE7VX*QGrOgo|LkC%?bQ(_$=9j|@AtcK1og`)W4KaM0G?r`;!^VGJJR91_q#Z8;Tw08rRODU^tqqGkEvv%6omaxQnSu)jNnSrpZq$%T?4O_`1K3uLDS-UmN<__)X%wwSqjz#p#?@4Z!&72M{^^NurA>0sU1|Rxca`X zpiSrT70FdIJ3?H|;o1msOoM6Q)ur>2$0>?V+rs)huB83t31dY1v+~5e?k;kS(leJY zG5C@`4y!ICo#04VmWC4bireVm4#xFU3)?Np=wk%uZXDm?C`38oUt83lA}>VqpW?=A zW*;%B9^^Z_bB^(+l@D^GzNd`WVkhaquP{u=xAjiat4mtKZi5qaRWe%3ecmN^F=D%kA(zwg%o!RR%@0{GKe- zGW96t0zS2JGK~Lg^tq~R7llYBz8CEIKZtcJc8tj)G$Ti26xbl|frq7*_W9`S|)$ z{do!mZ52x&g1|ztY7SV!>Q6;3gw^>a{@dp*2+a>IfMc~=C-bI5hzV_)6nJUvk~Z~+_;Q8K@b6Fv2Z0Tgz0q5$G!{}a-q?jWJjJHj45a^* zh`N)H=Y{EsfO4Z_LmM-6!W$bE(SoJw$?%lU^%sRVW=KZpdC!c%^dxdFjRRBEpw}Z< z`DwXvUv#)SRl<^_n@qa#oSR^}g5XNbslE_3+O$%h@?E(aJ|Tmvz6x^|ap#*z-4@?D zCixeuh`eBgz>=!Fv_;0j;(uKDi-}kADL*J?NP+osoXvY;G2~!loc1GJd80|oOQUV~a zPtbD$?ZaqAEt!`@7PWmM2zK0n1_w6*U;0Zb6a##t!BFwQP3XtXi3cYG=)0@@!T) zYi1Rz49#JcRrt-CS;Hcs8PlplFrnmUQkldG=oEypn*%}7E-w84Y3KiqTYkA$Y zvdURACfpR7Qwx_*Sh{Tegw?T?NIUZ?%C?^S<}lBOLeItdx&AFk8VU24;J4}%!UiM4=Fj`TR@b>gJ_=y&9BhfG~TU(fcO{Uca3fsWoT`Dg~Q)~=^*ec-S{#G ze**J20soF0_nHJGKY`z)!0BV&mVcYW--YQP03Ywhu?Lbs{8Y9c2Y%Hcd^vCla}w}p zf!7Vfy|c_Uh=$(ke-E1P4W@}XdQc%MNAgD8G~VfgfMhfSUjTe5T6-U|l9O}0N6?V2 z+kid(sbP#o-=@J{7J-8B{lK3B9&+RNJ390L-vfMx8~1Wsem)+yUjYB(X?Tx~Uj;mw zHVk@ou-CuYksks68{lKyI4OrQ>9-R2%fPR7<7*szGw}Zbez_aJ-NCm3-wzyrYjoD1 zu9z@?Q zgLsbvn*{7^c}V1R;ax>s=IG3^HP0mKdD-;J%Il=k(8{}4tX?KfDXS=(IL>SsD_eKh zy6D>IqQ$Ii#p)GNRu)~h0r%w?u(GvlmM)4eVr9$jx?}m;MXQ#vvL(?qYuB-|rP)TH zi`QXh$(mKGmaUH3D}SR73==YbtyRy7u8JnHJJZWfHX%TLKppk*=$9_LKa{S&yY77^KO3n zKMQo-JyJBR$TEyMJcEA%=7}FAMO^gKN>{Ta8YZ3*W>?$??`INb-Td@lE$C{MMDu6) zsRp|Fw`Tdb{)PON;3u6d{S#UKwk-eMO98qr$ +#include +#include +#include +#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 ATOM 0 +#define CONS 1 -#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 ISATOM(x) (~(x)&1) +#define VALUE(x) ((x)>>1) +#define OBJECT(t,v) ((v)<<1|(t)) -#define VALUE(x) ((x) >> 1) +#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 void Print(long); -static WORD GetList(void); -static WORD GetObject(void); -static void PrintObject(long); -static WORD Eval(WORD, WORD); +static struct Lisp q[1]; + +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; } diff --git a/lisp.h b/lisp.h deleted file mode 100644 index 3dd6e84..0000000 --- a/lisp.h +++ /dev/null @@ -1,180 +0,0 @@ -#ifndef SECTORLISP_H_ -#define SECTORLISP_H_ -#include -#include -#include - -/*───────────────────────────────────────────────────────────────────────────│─╗ -│ 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_ */ diff --git a/lisp.lds b/lisp.lds deleted file mode 100644 index 8c0d4ee..0000000 --- a/lisp.lds +++ /dev/null @@ -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; diff --git a/lisp.lisp b/lisp.lisp index 535a8a2..a7ec891 100644 --- a/lisp.lisp +++ b/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) diff --git a/realify.sed b/realify.sed deleted file mode 100644 index fa82fe5..0000000 --- a/realify.sed +++ /dev/null @@ -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// diff --git a/realify.sh b/realify.sh deleted file mode 100755 index 66d20b5..0000000 --- a/realify.sh +++ /dev/null @@ -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 "$@" diff --git a/start.S b/start.S deleted file mode 100644 index 5d311e8..0000000 --- a/start.S +++ /dev/null @@ -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