Two things were missing from the REPL's history rather than broken in it. The ring worked; nothing reached it by the key almost everyone presses, and nothing survived quitting Emacs. `<up>' and `<down>' are history on the line being typed and line motion everywhere else. The conditional is the whole point: the text above the prompt is a transcript people scroll back through, and a blunt binding would take that away to buy something `M-p' already does. Inside a form typed over several lines the keys still move between them, and reach history from the first. The search is prefix-filtered, which is comint's `...-from-input' pair and what a shell does. Each wrapper hands `this-command' over to the comint command it delegates to, because comint tells a continued search from a fresh one by looking for its own name in `last-command' -- without it the first press looks perfect and the second never moves. History is kept in `flan-repl-history' under `user-emacs-directory', through `locate-user-emacs-file' so that no-littering and a moved state directory take it along; the file and the size are both defcustoms. It is read when the mode starts and written on kill and on exit, but not when the ring is empty and not when the ring is shorter than the file -- a write replaces the file whole, so either would throw away a session somebody else's Emacs saved. The exit sweep is not installed in batch, where every prompt belongs to a test and the file it would land in belongs to whoever ran it. Entries are separated by a form feed on its own line rather than by comint's newline, which is also what is inside a multi-line form: with the default the file shreds one such form into fragments. The separator is let-bound around each call instead of set in the buffer, because `comint-write-input-ring' reads it inside a temporary buffer where a buffer-local value is invisible. And the multi-line case the recall made reachable: `comint-send-input' sends the process mark to *point*, and `comint-eol-on-send' carries it no further than one line, while `flan-repl-return' judged completeness over the whole input. Recall parks point where the typing stopped -- for an empty prompt, at the start of what was just recalled -- so RET on a recalled form sent nothing at all. It now sends what it measured. emacs/test-flan-repl.el covers both halves from a stub prompt, loaded from test-flan-cider.el for the reason test-flan-mode.el gives: `emacs/*.el' is already a dependency of that stanza, so a file here needs no build change. No daemon: history is a function from a ring and a position to what the buffer says. Seventeen checks, which cost that suite nothing measurable -- the whole of it still runs in 0.15s.
Flan is an experimental, ahead-of-time compiled Lisp for programs that need predictable memory use and a fast edit–run loop. It combines S-expressions, static types, explicit ownership, and a development session that can replace a function in a running program without resetting its state.
It is being built around games, but the interesting part is broader: a compiled language where the running program remains available for inspection, experimentation, and small changes.
In practical terms: you get parentheses, a debugger that would like to have a conversation, and no garbage collector quietly choosing the dramatic moment to join your frame loop.
What it has
- Native compilation through LLVM, plus an in-progress direct x86-64 backend.
- C-like data layout: structs, fixed arrays, pointers, slices, and explicit allocation. There is no garbage collector.
- Owned
VecandMapcontainers, plus checked moves and borrowing-oriented slice operations. - Generics, algebraic unions, enums, macros, packages,
defer, and a C FFI. - Conditions and restarts for recoverable failures and interactive debugging.
- A raylib package and a collection of ported raylib examples.
- Native, WASI, and web build targets. The cross targets are useful but less complete than the native development workflow.
The project is exploratory software, not a stable language release. Some features are deliberately refused while their semantics are still undecided; the compiler aims to say why rather than quietly accepting a partial version. It has opinions, but at least they arrive as error messages.
Quick start
Building requires a current OCaml/Dune toolchain, LLVM/Clang, and the native C toolchain. Raylib is only needed for programs that use the bundled graphics package.
dune build
dune exec ./bin/main.exe -- run web/examples/hello.flan
To build a standalone native executable:
dune exec ./bin/main.exe -- build web/examples/hello.flan -o hello
./hello
The falling-sand demo uses raylib:
dune exec ./bin/main.exe -- run sand.flan
Once you are iterating regularly, put the built executable on your PATH if
you want to use the shorter flan commands shown below.
The live development loop
Start a long-lived development session:
flan dev sand.flan
The program runs normally and publishes a local socket beside the source file. The bundled Emacs mode can attach to it, evaluate expressions in the live process, inspect a stopped program, and recompile a top-level function from the buffer. A body change takes effect on the next call; changing a function's signature is intentionally rejected. The program keeps its state, which is especially nice when you have finally arranged the sand into something almost worth saving.
To set up the mode:
(add-to-list 'load-path "~/path/to/flan/emacs")
(require 'flan-mode)
Then use M-x flan-dev to start and attach, or C-c C-z to attach to a
session started in a terminal. The editor workflow is documented in
emacs/MANUAL.md.
A small example
(defstruct AssetMissing [id i32])
(defn load-asset [id i32] i32
(signal (AssetMissing {.id id}))
100)
(defn asset-or-placeholder [id i32] i32
(restart-case (load-asset id)
(use-placeholder [] -1)))
(defn main [] ()
(handler-bind [(AssetMissing [_] (invoke-restart 'use-placeholder))]
(println (asset-or-placeholder 7))))
Here a missing asset signals a typed condition. The handler chooses a restart, so execution continues with a placeholder instead of requiring error values to be threaded through every caller. See web/examples/restart.flan for a runnable version.
Commands
flan check <file.flan> type-check a program
flan run <file.flan> [args...] build and run it
flan build <file.flan> [-o out] [options] build a native executable
flan dev <file.flan> [-s socket] start a live development session
Useful build options include --debug, --sanitize, --no-bounds-checks,
--x86, and --target=wasm32-wasi|web. run is native-only; cross-built
output should be run with an appropriate WASI runtime or browser. A .wasm
file is not a tiny native executable in a trench coat.
Project map
- web/index.html — language reference and fuller examples.
- spec-memory.md — ownership, containers, and generics.
- spec-conditions.md — conditions, handlers, and restarts.
- emacs/MANUAL.md — the interactive editor workflow.
- docs/BUILT.md — implementation rationale.
- NEXT.md — current work and known limits.
License
Flan is released under the MIT License. Third-party material under
vendor/ is distributed under its own licenses.