/*
 * Lisp Source mode for QEmacs.
 *
 * Copyright (c) 2000-2026 Charlie Gordon.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "qe.h"

/* TODO: lisp-indent = 2 */

#define LISP_LANG_LISP     1
#define LISP_LANG_ELISP    2
#define LISP_LANG_SCHEME   4
#define LISP_LANG_RACKET   8
#define LISP_LANG_CLOJURE  16
#define LISP_LANG_SANDBOX  32
#define LISP_LANG_XAOS     64
#define LISP_LANG_JANET    128

/*---------------- Lisp colors ----------------*/

static const char lisp_keywords[] = {
    "defun|let|let*|set|setq|prog1|progn|cond|if|unless|when|while|"
    "defsubst|remove|otherwise|dolist|incf|decf|boundp|"
    "and|or|not|case|eq|"
    "cons|list|concat|car|cdr|setcar|setcdr|nth|nthcdr|length|"
    "reverse|sort|"
    "caar|cadr|cdar|cddr|caddr|cadddr|"
    "lambda|"
    "\xCE\xBB|"  /* greek character lambda in UTF-8 */
    "mod|abs|max|min|log|logand|logior|logxor|ash|in|"
    "0+|1+|1-|<|>|<=|>=|-|+|*|/|=|<>|/=|"
};

static const char elisp_keywords[] = {
    /* elisp primitives */
    "eval|load|read|null|get|put|catch|throw|unwind-protect|atom|delete|"
    "dotimes|aset|aref|setplist|equal|fset|setq-default|pcase-let|"
    "consp|vectorp|listp|symbolp|stringp|numberp|zerop|functionp|integerp|"
    "assq|apply|funcall|mapatoms|mapc|mapcar|mapconcat|plist-get|plist-put|"
    "function|intern|intern-soft|copy-sequence|delete-dups|fboundp|"
    "push|pop|append|nconc|nreverse|memq|delq|remq|pcase|assoc|member|"
    "defalias|defgroup|defmacro|type-of|condition-case|declare-function|"
    "make-obsolete-variable|define-obsolete-variable-alias|set-default|"
    "default-boundp|default-value|car-safe|cdr-safe|"
    "make-variable-buffer-local|make-local-variable|local-variable-p|"
    "symbol-value|symbol-name|symbol-function|symbol-plist|"
    "string-match|downcase|upcase|string<|string=|format|substring|"
    "string-lessp|string-as-unibyte|"
    "format-time-string|current-time-string|"
    "string-to-number|number-to-string|read-from-string|char-to-string|"
    "make-string|string-to-char|string-equal|split-string|"
    "regexp-quote|"
    "make-vector|vector|vconcat|add-to-list|"
    /* emacs specific stuff */
    "eval-when-compile|assert|cl-assert|cl-pushnew|require|provide|"
    "interactive|save-excursion|save-restriction|error|message|sit-for|"
    "save-match-data|narrow-to-region|widen|"
    "call-interactively|run-hooks|add-hook|remove-hook|run-hook-with-args|"
    "defcustom|defvar|defconst|featurep|defvar-local|commandp|macrop|"
    "define-minor-mode|define-derived-mode|define-major-mode|"
    "define-key|make-keymap|make-sparse-keymap|key-binding|keymapp|lookup-key|"
    /* editing primitives */
    "marker-position|move-marker|copy-marker|set-marker|markerp|point-marker|"
    "mark|point|goto-char|char-after|preceding-char|following-char|"
    "current-column|"
    "move-beginning-of-line|move-end-of-line|beginning-of-line|end-of-line|"
    "count-lines|line-move|open-line|newline|"
    "region-beginning|region-end|line-beginning-position|line-end-position|"
    "line-beginning|line-end|bobp|eobp|bolp|eolp|"
    "forward-word|backward-word|forward-line|backward-line|"
    "forward-char|backward-char|skip-chars-forward|"
    "push-mark|point-min|point-max|exchange-point-and-mark|"
    "match-beginning|match-end|match-data|set-match-data|replace-match|"
    "search-forward|search-backward|re-search-forward|re-search-backward|"
    "looking-at|looking-back|"
    "display-buffer|erase-buffer|current-buffer|"
    "buffer-modified-p|set-buffer-modified-p|"
    "save-current-buffer|buffer-file-name|buffer-name|buffer-list|buffer-size|"
    "set-buffer|"
    "switch-to-buffer|get-buffer-create|kill-buffer|"
    "buffer-substring|buffer-substring-no-properties|"
    "set-text-properties|remove-text-properties|"
    "get-char-property|get-text-property|put-text-property|"
    "read-char|read-string|read-file-name|y-or-n-p|yes-or-no-p|"
    "completing-read|"
    "expand-file-name|file-name-directory|"
    "with-current-buffer|with-temp-buffer|"
    "with-syntax-table|syntax-table|standard-syntax-table|"
    "skip-syntax-forward|skip-syntax-backward|skip-chars-backward|"
    "insert|delete-char|delete-region|prin1|princ|terpri|indent-to|"
    "insert-file-contents|"
    "downcase-region|upcase-region|upcase-initials-region|"
    "delete-horizontal-space|kill-line|kill-region|yank|yank-pop|"
    "move-to-column|align|align-column|align-region|indent-region|"
    "write-region|undo-boundary|undo-in-progress|window-minibuffer-p|"
    "make-overlay|delete-overlay|remove-overlays|move-overlay|"
    "overlay-start|overlay-end|overlays-at|overlay-get|overlay-put|"
    "overlay-buffer|overlay-properties|"
    "define-abbrev|abbrev-get|abbrev-put|"
    "defface|make-face|set-face-property|facep|"
    "call-process|make-directory|delete-file|find-file|user-error|"
    "set-version-in-file|rx|submatch|read-directory-name|read-number|"
    "save-buffer|display-warning|file-readable-p|file-exists-p|"
    "file-directory-p|file-relative-name|make-text-button|"
    "string-prefix-p|sort-lines|write-file|pop-to-buffer|"
    "directory-files|default-directory|match-string|file-name-nondirectory|"
    "process-lines|emacs-major-version|emacs-minor-version|"
    "ignore-errors|define-button-type|button-get|find-file-noselect|"
    "eval-after-load|register-input-method|propertize|get-buffer|"
    "set-buffer-multibyte|current-time|read-event|noninteractive|"
    "frame-live-p|buffer-live-p|make-frame|selected-frame|select-frame|"
    "select-window|save-window-excursion|get-buffer-window|interactive-p|"
    "point-at-bol|load-file|locate-library|temp-directory|overlay|reparse-symbol|"
    "toggle-read-only|font-lock-mode|defimage|deftheme|defclass|defstruct|"
    "autoload|"
};

static const char scheme_keywords[] = {
    ""
};

static const char racket_keywords[] = {
    ""
};

static const char clojure_keywords[] = {
    ""
};

static const char sandbox_keywords[] = {
    ""
};

static const char lisp_types[] = {
    "nil|t|"
};

static const char elisp_types[] = {
    "nil|t|&optional|"
};

enum {
    IN_LISP_LEVEL    = 0x1F,    /* for IN_LISP_SCOMMENT */
    IN_LISP_COMMENT  = 0x20,
    IN_LISP_STRING   = 0x40,
    IN_LISP_SCOMMENT = 0x80,
};

enum {
    LISP_STYLE_TEXT       = QE_STYLE_DEFAULT,
    LISP_STYLE_COMMENT    = QE_STYLE_COMMENT,
    LISP_STYLE_SCOMMENT   = QE_STYLE_COMMENT,
    LISP_STYLE_NUMBER     = QE_STYLE_NUMBER,
    LISP_STYLE_STRING     = QE_STYLE_STRING,
    LISP_STYLE_CHARCONST  = QE_STYLE_STRING_Q,
    LISP_STYLE_KEYWORD    = QE_STYLE_KEYWORD,
    LISP_STYLE_TYPE       = QE_STYLE_TYPE,
    LISP_STYLE_QSYMBOL    = QE_STYLE_PREPROCESS,
    LISP_STYLE_MACRO      = QE_STYLE_TAG,
    LISP_STYLE_PREPROCESS = QE_STYLE_PREPROCESS,
};

static int lisp_get_symbol(char *buf, int buf_size, const char32_t *p)
{
    buf_t outbuf, *out;
    char32_t c;
    int i;

    out = buf_init(&outbuf, buf, buf_size);

    for (i = 0; (c = p[i]) != '\0'; i++) {
        if (qe_isblank(c) || qe_findchar(";(){}[]#'`,\"", c))
            break;
        buf_putc_utf8(out, c);
    }
    return i;
}

static int lisp_is_number(const char *str)
{
    int i;

    if (*str == 'b' && str[1]) {
        for (str++; qe_isbindigit(*str); str++)
            continue;
    } else
    if (*str == 'o' && str[1]) {
        for (str++; qe_isoctdigit(*str); str++)
            continue;
    } else
    if (*str == 'x' && str[1]) {
        for (str++; qe_isxdigit(*str); str++)
            continue;
    } else {
        if ((*str == '-' || *str == 'd') && str[1])
            str++;
        if (qe_isdigit(*str)) {
            for (; qe_isdigit(*str); str++)
                continue;
            if (*str == '.') {
                for (str++; qe_isdigit(*str); str++)
                    continue;
            }
            if (qe_tolower(*str) == 'e') {
                i = 1;
                if (str[i] == '+' || str[i] == '-')
                    i++;
                if (qe_isdigit(str[i])) {
                    for (str += i + 1; qe_isdigit(*str); str++)
                        continue;
                }
            }
        }
    }
    return (*str) ? 0 : 1;
}

static void lisp_colorize_line(QEColorizeContext *cp,
                               const char32_t *str, int n,
                               QETermStyle *sbuf, ModeDef *syn)
{
    int colstate = cp->colorize_state;
    int i = 0, start = i, len, level, style, style1, has_expr;
    int mode_flags = syn->colorize_flags;
    char kbuf[64];

    level = colstate & IN_LISP_LEVEL;
    style1 = style = 0;
    has_expr = 0;

    if (colstate & IN_LISP_SCOMMENT)
        style1 = LISP_STYLE_SCOMMENT;
    if (colstate & IN_LISP_STRING)
        goto parse_string;
    if (colstate & IN_LISP_COMMENT)
        goto parse_comment;

    while (i < n) {
        has_expr = 0;
        start = i;
        switch (str[i++]) {
        case ',':
            if (str[i] == '@')
                i++;
            FALLTHROUGH;
        case '`':
            style = LISP_STYLE_MACRO;
            break;
        case ';':
            if (mode_flags & LISP_LANG_JANET)
                goto regular;
            i = n;
            style = LISP_STYLE_COMMENT;
            break;
        case '(':
            if (colstate & IN_LISP_SCOMMENT)
                level++;
            break;
        case ')':
            if (colstate & IN_LISP_SCOMMENT) {
                if (level-- <= 1) {
                    SET_STYLE(sbuf, start, i - (level < 0), style1);
                    colstate &= ~IN_LISP_SCOMMENT;
                    level = 0;
                    style1 = 0;
                    continue;
                }
            }
            break;
        case '#':
            if (mode_flags & LISP_LANG_JANET) {
                i = n;
                style = LISP_STYLE_COMMENT;
                break;
            }
            if (str[i] == '|') {
                /* #| ... |# -> block comment */
                colstate |= IN_LISP_COMMENT;
                i++;
            parse_comment:
                for (; i < n; i++) {
                    if (str[i] == '|' && str[i + 1] == '#') {
                        i += 2;
                        colstate &= ~IN_LISP_COMMENT;
                        break;
                    }
                }
                style = LISP_STYLE_COMMENT;
                break;
            }
            if (str[i] == ';') {
                /* #; sexpr -> comment out sexpr */
                i++;
                colstate |= IN_LISP_SCOMMENT;
                style1 = LISP_STYLE_SCOMMENT;
                break;
            }
            if (str[i] == '"') {
                i++;
                colstate |= IN_LISP_STRING;
                goto parse_string;
            }
            if (str[i] == ':'
            &&  (str[i + 1] == '-' || qe_isalnum_(str[i + 1]))) {
                len = lisp_get_symbol(kbuf, sizeof(kbuf), str + i + 1);
                i += 1 + len;
                goto has_symbol;
            }
            if (qe_isalpha_(str[i])) {
                len = lisp_get_symbol(kbuf, sizeof(kbuf), str + i);
                i += len;
                if (strequal(kbuf, "t") || strequal(kbuf, "f")) {
                    /* #f -> false, #t -> true */
                    goto has_qsymbol;
                }
                if (mode_flags & LISP_LANG_RACKET) {
                    if (start == 0 && strequal(kbuf, "lang")) {
                        i = n;
                        style = LISP_STYLE_PREPROCESS;
                        break;
                    }
                    if (strequal(kbuf, "rx") || strequal(kbuf, "px")) {
                        if (str[i] == '"') {
                            /* #rx"regex" */
                            i += 1;
                            colstate |= IN_LISP_STRING;
                            goto parse_string;
                        }
                        if (str[i] == '#' && str[i + 1] == '"') {
                            /* #rx#"regex" */
                            i += 2;
                            colstate |= IN_LISP_STRING;
                            goto parse_string;
                        }
                    }
                }
                /* #b[01]+  -> binary constant */
                /* #o[0-7]+  -> octal constant */
                /* #d[0-9]+  -> decimal constant */
                /* #x[0-9a-fA-F]+  -> hex constant */
                goto has_symbol;
            }
            if (str[i] == '\\') {
                if (qe_isalnum_(str[i + 1])) {
                    /* #\x[0-9a-fA-F]+  -> hex char constant */
                    /* #\[a-zA-Z0-9]+  -> named char constant */
                    len = lisp_get_symbol(kbuf, sizeof(kbuf), str + i + 1);
                    i += 1 + len;
                    goto has_char_const;
                }
                if (i + 1 < n) {
                    i += 2;
                    goto has_char_const;
                }
            }
            {
                /* #( ... )  -> vector object */
                /* #! ... \n  -> line comment */
                /* # SPC  -> NIL ? */
            }
            break;
        case '"':
            /* parse string const */
            colstate |= IN_LISP_STRING;
        parse_string:
            while (i < n) {
                if (str[i] == '\\' && ++i < n) {
                    i++;
                } else
                if (str[i++] == '"') {
                    colstate &= ~IN_LISP_STRING;
                    has_expr = 1;
                    break;
                }
            }
            style = LISP_STYLE_STRING;
            break;
        case '?':
            /* parse char const */
            /* XXX: Should parse keys syntax */
            if (str[i] == '\\' && i + 1 < n) {
                i += 2;
            } else
            if (i < n) {
                i += 1;
            }
        has_char_const:
            has_expr = 1;
            style = LISP_STYLE_CHARCONST;
            break;
        case '\'':
            len = lisp_get_symbol(kbuf, sizeof(kbuf), str + i);
            if (len > 0) {
                i += len;
            has_qsymbol:
                has_expr = 1;
                style = LISP_STYLE_QSYMBOL;
                break;
            }
            break;
        default:
        regular:
            len = lisp_get_symbol(kbuf, sizeof(kbuf), str + i - 1);
            if (len > 0) {
                i += len - 1;
            has_symbol:
                has_expr = 1;
                if (lisp_is_number(kbuf)) {
                    style = LISP_STYLE_NUMBER;
                    break;
                }
                if (strfind(lisp_keywords, kbuf)
                ||  strfind(syn->keywords, kbuf)) {
                    style = LISP_STYLE_KEYWORD;
                    break;
                }
                if (strfind(syn->types, kbuf)) {
                    style = LISP_STYLE_TYPE;
                    break;
                }
                /* skip other symbol */
                break;
            }
            break;
        }
        if (style1) {
            style = style1;
            if (has_expr) {
                if ((colstate & IN_LISP_SCOMMENT) && level <= 0) {
                    colstate &= ~IN_LISP_SCOMMENT;
                    level = 0;
                    style1 = 0;
                }
            }
        }
        if (style) {
            SET_STYLE(sbuf, start, i, style);
            style = 0;
        }
    }
    colstate = (colstate & ~IN_LISP_LEVEL) | (level & IN_LISP_LEVEL);
    cp->colorize_state = colstate;
}

ModeDef lisp_mode = {
    .name = "Lisp",
    .extensions = "ll|li|lh|lo|lm|lisp|ls9",
    .keywords = NULL,
    .shell_handlers = "lisp",
    .types = lisp_types,
    .colorize_func = lisp_colorize_line,
    .colorize_flags = LISP_LANG_LISP,
};

static ModeDef elisp_mode = {
    .name = "ELisp",
    .extensions = "el",
    .filenames = ".emacs",
    .keywords = elisp_keywords,
    .types = elisp_types,
    .colorize_func = lisp_colorize_line,
    .colorize_flags = LISP_LANG_ELISP,
    .fallback = &lisp_mode,
};

static ModeDef scheme_mode = {
    .name = "Scheme",
    .extensions = "scm|sch|ss",
    .keywords = scheme_keywords,
    .types = lisp_types,
    .colorize_func = lisp_colorize_line,
    .colorize_flags = LISP_LANG_SCHEME,
    .fallback = &lisp_mode,
};

static ModeDef racket_mode = {
    .name = "Racket",
    .extensions = "rkt|rktd",
    .keywords = racket_keywords,
    .types = lisp_types,
    .colorize_func = lisp_colorize_line,
    .colorize_flags = LISP_LANG_RACKET,
    .fallback = &lisp_mode,
};

static ModeDef clojure_mode = {
    .name = "Clojure",
    .extensions = "clj|cljc",
    .keywords = clojure_keywords,
    .types = lisp_types,
    .colorize_func = lisp_colorize_line,
    .colorize_flags = LISP_LANG_CLOJURE,
    .fallback = &lisp_mode,
};

static ModeDef sandbox_mode = {
    .name = "Sandbox",  /* MacOS, Tinyscheme based? */
    .extensions = "sb",
    .keywords = sandbox_keywords,
    .types = lisp_types,
    .colorize_func = lisp_colorize_line,
    .colorize_flags = LISP_LANG_SANDBOX,
    .fallback = &lisp_mode,
};

static const char xaos_keywords[] = {
    ""
};

static ModeDef xaos_mode = {
    .name = "Xaos",  /* Xaos fractal generator */
    .extensions = "xhf|xaf|xpf",
    .keywords = xaos_keywords,
    .types = lisp_types,
    .colorize_func = lisp_colorize_line,
    .colorize_flags = LISP_LANG_XAOS,
    .fallback = &lisp_mode,
};

static const char janet_types[] = {
    "nil|t|false|true"
};

static const char janet_keywords[] = {
    /* from the documentation:
    % %= * *= *args* *current-file* *debug* *defdyn-prefix*
    *doc-color* *doc-width* *err* *err-color* *executable* *exit*
    *exit-value* *ffi-context* *flychecking* *lint-error*
    *lint-levels* *lint-warn* *macro-form* *macro-lints*
    *module-cache* *module-loaders* *module-loading* *module-make-env*
    *module-paths* *out* *peg-grammar* *pretty-format* *profilepath*
    *redef* *repl-prompt* *syspath* *task-id* + ++ += - -- -= -> ->>
    -?> -?>> / /= < <= = > >= abstract? accumulate accumulate2 all
    all-bindings all-dynamics and any? apply array array/clear
    array/concat array/ensure array/fill array/insert array/join
    array/new array/new-filled array/peek array/pop array/push
    array/remove array/slice array/trim array/weak array? as->
    as-macro as?-> asm assert assertf bad-compile bad-parse band
    blshift bnot boolean? bor brshift brushift buffer buffer/bit
    buffer/bit-clear buffer/bit-set buffer/bit-toggle buffer/blit
    buffer/clear buffer/fill buffer/format buffer/format-at
    buffer/from-bytes buffer/new buffer/new-filled buffer/popn
    buffer/push buffer/push-at buffer/push-byte buffer/push-float32
    buffer/push-float64 buffer/push-string buffer/push-uint16
    buffer/push-uint32 buffer/push-uint64 buffer/push-word
    buffer/slice buffer/trim buffer? bundle/add bundle/add-bin
    bundle/add-directory bundle/add-file bundle/add-manpage
    bundle/install bundle/installed? bundle/list bundle/manifest
    bundle/prune bundle/reinstall bundle/replace bundle/topolist
    bundle/uninstall bundle/update-all bundle/whois bxor bytes? cancel
    case catseq cfunction? chr cli-main cmp comment comp compare
    compare< compare<= compare= compare> compare>= compif compile
    complement comptime compwhen cond coro count curenv debug
    debug/arg-stack debug/break debug/fbreak debug/lineage debug/stack
    debug/stacktrace debug/step debug/unbreak debug/unfbreak debugger
    debugger-env debugger-on-status dec deep-not= deep= def- default
    default-peg-grammar defdyn defer defglobal defmacro defmacro- defn
    defn- delay describe dictionary? disasm distinct div doc doc*
    doc-format doc-of dofile drop drop-until drop-while dyn each eachk
    eachp edefer eflush empty? env-lookup eprin eprinf eprint eprintf
    error errorf ev/acquire-lock ev/acquire-rlock ev/acquire-wlock
    ev/all-tasks ev/call ev/cancel ev/capacity ev/chan ev/chan-close
    ev/chunk ev/close ev/count ev/deadline ev/do-thread ev/full
    ev/gather ev/give ev/give-supervisor ev/go ev/go-gather ev/lock
    ev/read ev/release-lock ev/release-rlock ev/release-wlock
    ev/rselect ev/rwlock ev/select ev/sleep ev/spawn ev/spawn-thread
    ev/take ev/thread ev/thread-chan ev/to-file ev/with-deadline
    ev/with-lock ev/with-rlock ev/with-wlock ev/write eval eval-string
    even? every? extreme false? ffi/align ffi/call
    ffi/calling-conventions ffi/close ffi/context ffi/defbind
    ffi/defbind-alias ffi/free ffi/jitfn ffi/lookup ffi/malloc
    ffi/native ffi/pointer-buffer ffi/pointer-cfunction ffi/read
    ffi/signature ffi/size ffi/struct ffi/trampoline ffi/write
    fiber-fn fiber/can-resume? fiber/current fiber/getenv
    fiber/last-value fiber/maxstack fiber/new fiber/root fiber/setenv
    fiber/setmaxstack fiber/status fiber? file/close file/flush
    file/lines file/open file/read file/seek file/tell file/temp
    file/write filewatch/add filewatch/listen filewatch/new
    filewatch/remove filewatch/unlisten filter find find-index first
    flatten flatten-into flush flycheck for forever forv freeze
    frequencies from-pairs function? gccollect gcinterval
    gcsetinterval generate gensym geomean get get-in getline getproto
    group-by has-key? has-value? hash idempotent? identity if-let
    if-not if-with import import* in inc index-of indexed? int/s64
    int/to-bytes int/to-number int/u64 int? interleave interpose
    invert janet/build janet/config-bits janet/version juxt juxt* keep
    keep-syntax keep-syntax! keys keyword keyword/slice keyword? kvs
    label last length lengthable? let load-image load-image-dict loop
    macex macex1 maclintf make-env make-image make-image-dict map
    mapcat marshal match math/-inf math/abs math/acos math/acosh
    math/asin math/asinh math/atan math/atan2 math/atanh math/cbrt
    math/ceil math/cos math/cosh math/e math/erf math/erfc math/exp
    math/exp2 math/expm1 math/floor math/frexp math/gamma math/gcd
    math/hypot math/inf math/int-max math/int-min math/int32-max
    math/int32-min math/lcm math/ldexp math/log math/log-gamma
    math/log10 math/log1p math/log2 math/nan math/next math/pi
    math/pow math/random math/rng math/rng-buffer math/rng-int
    math/rng-uniform math/round math/seedrandom math/sin math/sinh
    math/sqrt math/tan math/tanh math/trunc max max-of mean memcmp
    merge merge-into merge-module min min-of mod
    module/add-file-extension module/add-paths module/add-syspath
    module/cache module/expand-path module/find module/loaders
    module/loading module/paths module/value nan? nat? native neg?
    net/accept net/accept-loop net/address net/address-unpack
    net/chunk net/close net/connect net/flush net/listen net/localname
    net/peername net/read net/recv-from net/send-to net/server
    net/setsockopt net/shutdown net/socket net/write next nil? not
    not= number? odd? one? or os/arch os/cd os/chmod os/clock
    os/compiler os/cpu-count os/cryptorand os/cwd os/date os/dir
    os/environ os/execute os/exit os/getenv os/getpid os/isatty
    os/link os/lstat os/mkdir os/mktime os/open os/perm-int
    os/perm-string os/pipe os/posix-chroot os/posix-exec os/posix-fork
    os/proc-close os/proc-kill os/proc-wait os/readlink os/realpath
    os/rename os/rm os/rmdir os/setenv os/setlocale os/shell
    os/sigaction os/sleep os/spawn os/stat os/strftime os/symlink
    os/time os/touch os/umask os/which pairs parse parse-all
    parser/byte parser/clone parser/consume parser/eof parser/error
    parser/flush parser/has-more parser/insert parser/new
    parser/produce parser/state parser/status parser/where partial
    partition partition-by peg/compile peg/find peg/find-all peg/match
    peg/replace peg/replace-all pos? postwalk pp prewalk prin prinf
    print printf product prompt propagate protect put put-in quit
    range reduce reduce2 repeat repl require resume return reverse
    reverse! root-env run-context sandbox scan-number seq setdyn
    short-fn signal slice slurp some sort sort-by sorted sorted-by
    spit stderr stdin stdout string string/ascii-lower
    string/ascii-upper string/bytes string/check-set string/find
    string/find-all string/format string/from-bytes string/has-prefix?
    string/has-suffix? string/join string/repeat string/replace
    string/replace-all string/reverse string/slice string/split
    string/trim string/triml string/trimr string? struct
    struct/getproto struct/proto-flatten struct/rawget struct/to-table
    struct/with-proto struct? sum symbol symbol/slice symbol? table
    table/clear table/clone table/getproto table/new
    table/proto-flatten table/rawget table/setproto table/to-struct
    table/weak table/weak-keys table/weak-values table? tabseq take
    take-until take-while thaw thaw-keep-keys toggle trace tracev
    true? truthy? try tuple tuple/brackets tuple/join tuple/setmap
    tuple/slice tuple/sourcemap tuple/type tuple? type unless
    unmarshal untrace update update-in use values var- varfn varglobal
    walk warn-compile when when-let when-with with with-dyns with-env
    with-syms with-vars xprin xprinf xprint xprintf yield zero?
    zipcoll
     */

    "*=|"
    "++|"
    "+=|"
    "--|"
    "-=|"
    "/=|"
    ":close|"
    ":open|"
    ":read|"
    ":wait|"
    ":write|"
    "all|"
    "any?|"
    "apply|"
    "array?|"
    "array|"
    "as-macro|"
    "asm|"
    "assertf|"
    "assert|"
    "boolean?|"
    "break|"
    "buffer?|"
    "buffer|"
    "cfunction?|"
    "cmp|"
    "comment|"
    "compare<=|"
    "compare<|"
    "compare>=|"
    "compare>|"
    "compare|"
    "coro|"
    "count|"
    "debug|"
    "dec|"
    "deep-not=|"
    "deep=|"
    "def-|"
    "default|"
    "defdyn|"
    "defer|"
    "defglobal|"
    "defmacro-|"
    "defmacro|"
    "defn-|"
    "defn|"
    "def|"
    "disasm|"
    "do|"
    "each|"
    "eflush|"
    "empty?|"
    "eprinf|"
    "eprintf|"
    "eprint|"
    "errorf|"
    "error|"
    "even?|"
    "every?|"
    "false?|"
    "fiber?|"
    "flush|"
    "fn|"
    "forever|"
    "for|"
    "function?|"
    "gccollect|"
    "gensym|"
    "get|"
    "idempotent?|"
    "if-let|"
    "if-not|"
    "if|"
    "import|"
    "inc|"
    "keep|"
    "keyword?|"
    "loop|"
    "maclintf|"
    "map|"
    "nan?|"
    "nil?|"
    "not=|"
    "number?|"
    "odd?|"
    "pp|"
    "printf|"
    "print|"
    "prin|"
    "protect|"
    "put|"
    "quasiquote|"
    "quote|"
    "range|"
    "repeat|"
    "resume|"
    "seq|"
    "set|"
    "slice|"
    "some|"
    "splice|"
    "string?|"
    "string|"
    "struct?|"
    "struct|"
    "symbol?|"
    "table?|"
    "table|"
    "toggle|"
    "tracev|"
    "true?|"
    "truthy?|"
    "try|"
    "tuple?|"
    "tuple|"
    "unquote|"
    "upscope|"
    "var-|"
    "var|"
    "when-let|"
    "while|"
    "with|"
    "yield|"
    "zero?|"
};

static ModeDef janet_mode = {
    .name = "Janet",
    .extensions = "janet",
    .keywords = janet_keywords,
    .types = janet_types,
    .colorize_func = lisp_colorize_line,
    .colorize_flags = LISP_LANG_JANET,
    .fallback = &lisp_mode,
};

static int lisp_init(QEmacsState *qs)
{
    qe_register_mode(qs, &lisp_mode, MODEF_SYNTAX);
    qe_register_mode(qs, &elisp_mode, MODEF_SYNTAX);
    qe_register_mode(qs, &scheme_mode, MODEF_SYNTAX);
    qe_register_mode(qs, &racket_mode, MODEF_SYNTAX);
    qe_register_mode(qs, &clojure_mode, MODEF_SYNTAX);
    qe_register_mode(qs, &sandbox_mode, MODEF_SYNTAX);
    qe_register_mode(qs, &xaos_mode, MODEF_SYNTAX);
    qe_register_mode(qs, &janet_mode, MODEF_SYNTAX);

    return 0;
}

qe_module_init(lisp_init);
