Emacs binds it in emacs-lisp-mode-map and nowhere else, so a mode derived from lisp-mode inherits nothing and the key answers "undefined" — which reads as the client being broken rather than as the key being free.
498 lines
24 KiB
EmacsLisp
498 lines
24 KiB
EmacsLisp
;;; flan-mode.el --- Major mode for Flan -*- lexical-binding: t; -*-
|
|
|
|
;; Author: Joseph Ferano <joseph@ferano.io>
|
|
;; Version: 0.1.0
|
|
;; Package-Requires: ((emacs "29.1"))
|
|
;; Keywords: languages, lisp, tools
|
|
|
|
;; The headers above are what make this directory installable. M-x
|
|
;; package-install-file on it reads them, and a file with no Version: is not a
|
|
;; package as far as package.el is concerned -- until now the client was
|
|
;; reachable only by adding it to load-path by hand, which is a thing to
|
|
;; explain to every person who wants to try it.
|
|
;;
|
|
;; 29.1 is the floor because it is the oldest Emacs any of this has been run
|
|
;; against, not because some function here is known to need it. dape, which
|
|
;; flan-dape drives, asks for 29.1 as well and is a soft dependency: it is
|
|
;; reached through declare-function, so the rest of the client loads and works
|
|
;; without it and it is deliberately not listed above. The compiler this talks
|
|
;; to is not an Emacs package and cannot be listed here either -- emacs/MANUAL.md
|
|
;; says what has to be on PATH.
|
|
|
|
;; Derived from `prog-mode', borrowing `lisp-mode''s machinery for the parts
|
|
;; that are simply s-expressions: sexp motion, paren matching and
|
|
;; `beginning-of-defun' already do the right thing.
|
|
;;
|
|
;; Indentation is the part that does not, and it is ported from
|
|
;; `clojure-mode''s source rather than from `lisp-mode''s. That is a
|
|
;; deliberate change of reference and it is worth being exact about what it
|
|
;; means: `clojure-mode' is neither an ancestor nor a dependency here — this
|
|
;; mode ships in the Flan repository and requires nothing outside stock Emacs —
|
|
;; it is the file whose rules were read and written out again below.
|
|
;;
|
|
;; The reason is that Emacs Lisp has none of the shapes Flan is written in. It
|
|
;; has no vectors-as-bindings, no maps, no bracket variety, so
|
|
;; `lisp-indent-function' treats `[a 1 b 2]' as a function call and aligns
|
|
;; continuation lines under `1' — the first *argument* — instead of under `a',
|
|
;; the first *binding*. That was the reported bug in `sand.flan''s `settle',
|
|
;; and it was never one missing rule: every rule has to be added by hand when
|
|
;; the base language does not have the shape. Clojure's rules already cover
|
|
;; brackets-mean-binding, pairs-align, maps and `#_'.
|
|
;;
|
|
;; Where Flan diverges from Clojure it diverges on purpose, and each divergence
|
|
;; is written down at the place that handles it:
|
|
;;
|
|
;; - `defn' carries a **return type between the parameter vector and the
|
|
;; body**, and it is optional — `(defn show [x f32] …)' has none. Both are
|
|
;; handled by not caring: the spec is `:defn', so everything after the head
|
|
;; indents two, which is right for the name, the parameters, a return type
|
|
;; that is there and a body whether or not one preceded it.
|
|
;; - field access is `(.x v)', an ordinary call whose head happens to begin
|
|
;; with a dot. `.' is a symbol constituent in the syntax table below, so
|
|
;; nothing special is needed for it to read as a head.
|
|
;; - a field label is a dot: `{.x 1.0}' is a struct literal, and the colon
|
|
;; now belongs to keywords, which a `Map' will use as keys. The indenter
|
|
;; is correct for both *because* it never looks at the key: a brace aligns
|
|
;; under its first element whatever that element is spelled like.
|
|
|
|
;;; Code:
|
|
|
|
(require 'lisp-mode)
|
|
;; `thing-at-point', which the indenter reads the enclosing form's head with.
|
|
(require 'thingatpt)
|
|
|
|
;; Bound by `calculate-lisp-indent' around the call to `lisp-indent-function',
|
|
;; and declared in `lisp-mode' without a `defvar', so say so here rather than
|
|
;; let the byte-compiler call it a free variable.
|
|
(defvar calculate-lisp-indent-last-sexp)
|
|
;; For `imenu-generic-expression', which is set below and which would
|
|
;; otherwise be made buffer-local before its own defvar had run.
|
|
(require 'imenu)
|
|
|
|
;; The keymap binds them, but do not load the client merely to edit a file.
|
|
;; These must be real autoloads, not just `declare-function`s: otherwise a
|
|
;; user who has loaded only flan-mode cannot invoke M-x flan at all.
|
|
(autoload 'flan-eval-defun "flan" nil t)
|
|
(autoload 'flan-eval-buffer "flan" nil t)
|
|
(autoload 'flan-eval-last-sexp "flan" nil t)
|
|
(autoload 'flan-connect "flan" nil t)
|
|
(autoload 'flan-disconnect "flan" nil t)
|
|
(autoload 'flan-describe "flan" nil t)
|
|
(autoload 'flan-show-output "flan" nil t)
|
|
(autoload 'flan-repl "flan-repl" nil t)
|
|
(autoload 'flan-break "flan" nil t)
|
|
;; The two CIDER-shaped buffers. They reach the daemon through an indirection
|
|
;; of their own so that fixtures can drive them, so autoloading is all the
|
|
;; wiring they need.
|
|
(autoload 'flan-inspect "flan-inspect" nil t)
|
|
(autoload 'flan-cnr-show "flan-cnr" nil t)
|
|
(autoload 'flan-doc "flan" nil t)
|
|
(autoload 'flan "flan" nil t)
|
|
(autoload 'flan-quit "flan" nil t)
|
|
(autoload 'flan-restart-program "flan" nil t)
|
|
;; And the cheap counterpart, which needs an autoload for the reason above and
|
|
;; more than most: it is the command someone reaches for the moment a window
|
|
;; closes, which can be the first thing they ever ask the client to do.
|
|
(autoload 'flan-rerun "flan" nil t)
|
|
;; Bound below, like the rest, and it was the one missing an autoload.
|
|
(autoload 'flan-disassemble "flan" nil t)
|
|
;; The other question about the same function, and the reason it is a second
|
|
;; command rather than a fifth argument to the first: `flan-disassemble' asks
|
|
;; the running program, and this compiles the file.
|
|
(autoload 'flan-lowering "flan-lower" nil t)
|
|
;; C-c C-m and the half of it that is findable by name rather than by a
|
|
;; modifier. Real autoloads for the reason stated above: a `declare-function'
|
|
;; would leave M-x with nothing to load.
|
|
(autoload 'flan-macroexpand "flan" nil t)
|
|
(autoload 'flan-macroexpand-all "flan" nil t)
|
|
|
|
(defgroup flan nil
|
|
"Editing and evaluating Flan."
|
|
:group 'languages
|
|
:prefix "flan-")
|
|
|
|
(defconst flan--definers
|
|
'("defn" "defvar" "defconst" "defstruct" "defdata" "defunion" "defenum"
|
|
"defalias"
|
|
"declare" "import" "package")
|
|
"Forms that introduce a top-level name.")
|
|
|
|
(defconst flan--special
|
|
'("let" "if" "do" "while" "until" "dotimes" "loop" "match" "set" "return"
|
|
"defer" "some" "none" "try" "zeroed" "uninit" "slice" "at" "len" "addr"
|
|
"bytes" "cast" "true" "false" "nil" "print" "println")
|
|
"Forms with meaning to the checker.")
|
|
|
|
(defvar flan-font-lock-keywords
|
|
`((,(concat "(" (regexp-opt flan--definers t) "\\_>"
|
|
"[ \t]*\\(\\(?:\\sw\\|\\s_\\)+\\)?")
|
|
(1 font-lock-keyword-face)
|
|
(2 font-lock-function-name-face nil t))
|
|
(,(concat "(" (regexp-opt flan--special t) "\\_>") 1 font-lock-keyword-face)
|
|
;; A keyword resolves against an enum at the call site, so it reads as a
|
|
;; constant rather than as a string.
|
|
("\\_<:\\(?:\\sw\\|\\s_\\)+" . font-lock-constant-face)
|
|
;; A field. The label in a struct literal — `{.x 1.0}' — and the accessor
|
|
;; `(.x v)' are the same name and are drawn the same way. Without this
|
|
;; rule every field label in the corpus is unfontified, which is what the
|
|
;; colon-to-dot change left behind: the keyword rule above used to cover
|
|
;; them and no longer does, because the colon belongs to keywords now.
|
|
("\\_<\\.\\(?:\\sw\\|\\s_\\)+" . font-lock-constant-face)
|
|
;; The machine types, which are ordinary symbols but never anything else.
|
|
("\\_<\\(?:[iu]\\(?:8\\|16\\|32\\|64\\)\\|f\\(?:32\\|64\\)\\|bool\\|string\\|Unit\\|Never\\|Ptr\\|Option\\)\\_>"
|
|
. font-lock-type-face)
|
|
("\\_<\\(?:0x[0-9a-fA-F]+\\|-?[0-9]+\\(?:\\.[0-9]+\\)?\\)\\_>"
|
|
. font-lock-constant-face))
|
|
"Font lock for `flan-mode'.")
|
|
|
|
(defconst flan--name-re "\\(\\(?:\\sw\\|\\s_\\)+\\)"
|
|
"A Flan name, as one group.
|
|
Written in terms of the syntax table rather than as a character class, so
|
|
that the characters a name may contain are stated in one place — the table
|
|
below — and not again here.")
|
|
|
|
;; Anchored at the start of a line, which is where a top-level form is: a
|
|
;; `defn' nested inside a `let' is not a definition of anything, and a match
|
|
;; that ignored the column would offer one.
|
|
(defvar flan-imenu-generic-expression
|
|
`(("Functions" ,(concat "^(defn\\s-+" flan--name-re) 1)
|
|
("Types" ,(concat "^(def\\(?:struct\\|data\\|union\\|enum\\|alias\\)\\s-+"
|
|
flan--name-re)
|
|
1)
|
|
("Variables" ,(concat "^(def\\(?:var\\|const\\)\\s-+" flan--name-re) 1)
|
|
;; A forward declaration is not a definition, and a file with both would
|
|
;; otherwise show the same name twice with nothing to tell them apart.
|
|
("Declared" ,(concat "^(declare\\s-+" flan--name-re) 1))
|
|
"Imenu index for `flan-mode', by what each form introduces.")
|
|
|
|
(defun flan-current-defun-name ()
|
|
"The name of the top-level definition point is in, or nil.
|
|
For `which-func-functions': a long file scrolled into the middle of a
|
|
function is the case this exists for, and it is the case where the header
|
|
line is off screen."
|
|
(save-excursion
|
|
(ignore-errors
|
|
(beginning-of-defun)
|
|
(and (looking-at (concat "(" (regexp-opt flan--definers t) "\\_>\\s-+"
|
|
flan--name-re))
|
|
(match-string-no-properties 2)))))
|
|
|
|
(defvar flan-mode-syntax-table
|
|
(let ((table (make-syntax-table lisp-mode-syntax-table)))
|
|
;; Flan's own punctuation in names: a name may contain - ? > / and .
|
|
(modify-syntax-entry ?? "_" table)
|
|
(modify-syntax-entry ?! "_" table)
|
|
(modify-syntax-entry ?/ "_" table)
|
|
(modify-syntax-entry ?. "_" table)
|
|
(modify-syntax-entry ?- "_" table)
|
|
;; [ ] and { } are brackets, not symbol characters: every binding list and
|
|
;; every type is written with them.
|
|
(modify-syntax-entry ?\[ "(]" table)
|
|
(modify-syntax-entry ?\] ")[" table)
|
|
(modify-syntax-entry ?{ "(}" table)
|
|
(modify-syntax-entry ?} "){" table)
|
|
table)
|
|
"Syntax table for `flan-mode'.")
|
|
|
|
(defvar flan-mode-map
|
|
(let ((map (make-sparse-keymap)))
|
|
;; Autoloaded from flan.el, so the client loads on first use.
|
|
(define-key map (kbd "C-c C-c") #'flan-eval-defun)
|
|
;; The same command on the binding SLIME and CIDER put it on. Emacs binds
|
|
;; C-M-x to eval-defun only in `emacs-lisp-mode-map', so a mode derived
|
|
;; from `lisp-mode' inherits nothing and the key is undefined — which
|
|
;; reads as the client being broken rather than as the key being free.
|
|
(define-key map (kbd "C-M-x") #'flan-eval-defun)
|
|
(define-key map (kbd "C-c C-k") #'flan-eval-buffer)
|
|
(define-key map (kbd "C-x C-e") #'flan-eval-last-sexp)
|
|
(define-key map (kbd "C-c C-z") #'flan-connect)
|
|
(define-key map (kbd "C-c C-q") #'flan-disconnect)
|
|
(define-key map (kbd "C-c C-d") #'flan-describe)
|
|
(define-key map (kbd "C-c C-o") #'flan-show-output)
|
|
(define-key map (kbd "C-c C-r") #'flan-repl)
|
|
;; C-c C-b opens the buffer rather than the minibuffer prompt: it shows the
|
|
;; same restarts plus the condition and the stack, and — the reason it
|
|
;; replaces rather than joins — it is the one that refuses a *shadowed*
|
|
;; restart instead of silently invoking a different frame's. flan-break is
|
|
;; still there under C-c C-M-b for the one-key path.
|
|
(define-key map (kbd "C-c C-b") #'flan-cnr-show)
|
|
(define-key map (kbd "C-c C-M-b") #'flan-break)
|
|
(define-key map (kbd "C-c C-i") #'flan-inspect)
|
|
;; Help on the name at point. C-c C-d is taken by `flan-describe', which
|
|
;; is about the session rather than about a name, and renaming a key that
|
|
;; is already documented costs more than it is worth. Not C-c C-h either:
|
|
;; C-h after a prefix is how anyone finds out what is under C-c, and a
|
|
;; binding there takes that away.
|
|
(define-key map (kbd "C-c C-v") #'flan-doc)
|
|
;; The code the running program is calling for a name, as amd64 or as the
|
|
;; IR it was built from. C-u for the IR rather than a second key: it is
|
|
;; the same question asked of the same body.
|
|
(define-key map (kbd "C-c C-a") #'flan-disassemble)
|
|
;; Every lowering of the name at point -- the IR, `llc' at -O0 and at -O2,
|
|
;; and the hand-written x86 backend -- in one buffer of folding sections.
|
|
;; Beside C-c C-a and not under it: that one answers for the program that
|
|
;; is running, this one for the file on disk, and no prefix argument can
|
|
;; make one of those into the other.
|
|
(define-key map (kbd "C-c C-l") #'flan-lowering)
|
|
;; What the macro call before point expands to. CIDER's key, and `C-u' for
|
|
;; the fixpoint rather than a second one — the same question asked of the
|
|
;; same form, which is the rule `C-c C-a' above already follows. One step
|
|
;; is the bare key because it is the one that can name the macro that ran:
|
|
;; a full expansion is stamped with the outermost name only.
|
|
(define-key map (kbd "C-c C-m") #'flan-macroexpand)
|
|
;; The way out when a reload is refused: rebuild, relaunch, reconnect.
|
|
(define-key map (kbd "C-c C-x") #'flan-restart-program)
|
|
;; And beside it the cheap one, which is the same question — "run this
|
|
;; program" — asked of a process that is already there: `main' again, with
|
|
;; the globals as the finished run left them. The pairing is `C-c C-b'
|
|
;; and `C-c C-M-b' above: the modifier is what distinguishes two commands
|
|
;; about one subject, and the heavier of the pair keeps the bare key
|
|
;; because it is the one that works from any state.
|
|
(define-key map (kbd "C-c C-M-x") #'flan-rerun)
|
|
map)
|
|
"Keymap for `flan-mode'.")
|
|
|
|
;;;###autoload
|
|
(define-derived-mode flan-mode prog-mode "Flan"
|
|
"Major mode for editing Flan.
|
|
|
|
\\{flan-mode-map}"
|
|
:syntax-table flan-mode-syntax-table
|
|
(setq-local comment-start ";")
|
|
(setq-local comment-start-skip ";+ *")
|
|
(setq-local comment-add 1)
|
|
(setq-local font-lock-defaults '(flan-font-lock-keywords))
|
|
(setq-local indent-line-function #'lisp-indent-line)
|
|
;; Spaces. The whole corpus is written with them, and alignment that is
|
|
;; correct here is alignment under a specific *column* — a tab makes that
|
|
;; depend on a setting the file cannot carry.
|
|
(setq-local indent-tabs-mode nil)
|
|
(setq-local lisp-indent-function #'flan-indent-function)
|
|
(setq-local outline-regexp ";;;;+[ \t]*")
|
|
(setq-local imenu-generic-expression flan-imenu-generic-expression)
|
|
;; Buffer-locally, because this answers for Flan and nothing else.
|
|
(add-hook 'which-func-functions #'flan-current-defun-name nil t))
|
|
|
|
;;; Indentation
|
|
|
|
;; Ported from `clojure-mode', as the header says. Three pieces, and the
|
|
;; middle one is where the reported bug lived.
|
|
|
|
(defconst flan-indent-specs
|
|
;; A number N: the first N arguments are *special* and the rest are a body.
|
|
;; `:defn': everything after the head is a body. These are the same two
|
|
;; values `clojure-mode' uses, and they mean the same thing here.
|
|
'(;; Binding forms. The vector is the one special argument; the body
|
|
;; follows it. This is the entry that makes `let' correct, but note that
|
|
;; it is *not* what fixed the reported bug — alignment inside the vector is
|
|
;; `flan--data-form-p' below, and it would be right even with no entry
|
|
;; here.
|
|
("let" . 1)
|
|
("loop" . 1)
|
|
("dotimes" . 1)
|
|
;; The clause vector, then the protected body. Same shape as `let'.
|
|
("handler-bind" . 1)
|
|
("handler-case" . 1)
|
|
;; Test first, body after.
|
|
("if" . 1)
|
|
("when" . 1)
|
|
("unless" . 1)
|
|
("while" . 1)
|
|
("until" . 1)
|
|
("match" . 1)
|
|
("with-allocator" . 1)
|
|
;; The protected form, then the clauses. It has to be 1 rather than 0:
|
|
;; with 0 the clauses are ordinary arguments, and a protected form written
|
|
;; on the head's line — `(restart-case (middle n)' — would drag every
|
|
;; clause out to align under it.
|
|
("restart-case" . 1)
|
|
;; All body.
|
|
("do" . 0)
|
|
("cond" . 0)
|
|
("defer" . 0)
|
|
("try" . 0)
|
|
;; `defn' is `:defn' rather than a count because the return type between
|
|
;; the parameters and the body is optional; a count would have to know
|
|
;; whether one is there, and `:defn' does not care.
|
|
("defn" . :defn)
|
|
;; `(declare-c NAME [params] RET "CSymbol")'. The name is the one special
|
|
;; argument; everything after it is written down the page in one column.
|
|
("declare" . 1)
|
|
("declare-c" . 1))
|
|
"How each form indents, by name.
|
|
Anything not named here that begins with `def' is treated as `:defn' by
|
|
`flan-indent-function'; anything else indents as a function call.")
|
|
|
|
(defun flan--indent-spec (name)
|
|
"The indent spec for the form called NAME, or nil."
|
|
(and name (cdr (assoc name flan-indent-specs))))
|
|
|
|
(defun flan--non-logical-sexp-p ()
|
|
"Non-nil if what follows point is read but produces no form.
|
|
Today that is only `#_', the discard reader macro — see `lib/reader.ml'. A
|
|
discarded form is skipped rather than counted, so `(let [#_a b 1] …)' still
|
|
aligns as the pairs it will be once the reader is done with it."
|
|
(looking-at-p "#_"))
|
|
|
|
(defun flan--forward-sexp (&optional n)
|
|
"Move forward over N sexps, skipping discarded ones."
|
|
(setq n (or n 1))
|
|
(let ((forward-sexp-function nil))
|
|
(while (> n 0)
|
|
(while (flan--non-logical-sexp-p) (forward-sexp 1))
|
|
(forward-sexp 1)
|
|
(setq n (1- n)))))
|
|
|
|
(defun flan--backward-sexp (&optional n)
|
|
"Move backward over N sexps, skipping discarded ones."
|
|
(setq n (or n 1))
|
|
(let ((forward-sexp-function nil))
|
|
(while (> n 0)
|
|
(backward-sexp 1)
|
|
(while (and (not (bobp))
|
|
(ignore-errors
|
|
(save-excursion (backward-sexp 1)
|
|
(flan--non-logical-sexp-p))))
|
|
(backward-sexp 1))
|
|
(setq n (1- n)))))
|
|
|
|
(defun flan--data-form-p ()
|
|
"Non-nil if the form at point is data rather than a call.
|
|
Point is on the opening delimiter.
|
|
|
|
**This is the fix.** A `[' or a `{' is not a function call, so nothing in it
|
|
is an argument and there is no first argument to align under. Aligning under
|
|
the first *element* instead is what makes a binding vector line its names up
|
|
name-under-name, and by the same rule it lines up `defn' parameter lists,
|
|
`restart-case' clause parameters, `handler-bind' clause vectors, and a brace
|
|
whatever its keys are spelled like — `{.x 1}' and `{:north 0}' — with no case
|
|
for any of them, because the rule is about the bracket and never about what is
|
|
written inside it.
|
|
|
|
A head that is not a symbol is the third case: `((f x) y)' has nothing to look
|
|
a spec up under."
|
|
(or (memq (char-after) '(?\[ ?\{))
|
|
(not (looking-at ".\\(?:\\sw\\|\\s_\\)"))))
|
|
|
|
(defun flan--normal-indent (last-sexp)
|
|
"Align with the argument above, as an ordinary call does.
|
|
Point is just after the open paren of the enclosing form; LAST-SEXP is where
|
|
the sexp before the one being indented starts."
|
|
(goto-char last-sexp)
|
|
(forward-sexp 1)
|
|
(flan--backward-sexp 1)
|
|
(let ((last-sexp-start nil))
|
|
(if (ignore-errors
|
|
;; Back up until we reach a sexp that starts its own line: that is
|
|
;; the one every Lisp aligns under.
|
|
(while (string-match "[^[:blank:]]"
|
|
(buffer-substring (line-beginning-position)
|
|
(point)))
|
|
(setq last-sexp-start (prog1 (point) (forward-sexp -1))))
|
|
t)
|
|
(current-column)
|
|
;; Nothing above but the head itself, so there are two cases and Flan
|
|
;; answers them differently from Clojure's default.
|
|
(if (and last-sexp-start (< last-sexp-start (line-end-position)))
|
|
;; An argument shares the head's line. Align under it — this is the
|
|
;; alignment every Lisp agrees on.
|
|
(progn (goto-char last-sexp-start) (current-column))
|
|
;; The head is alone on its line. Clojure's default would align the
|
|
;; arguments under the *head*; the Flan corpus indents them by a body
|
|
;; instead, which is `clojure-indent-style''s `align-arguments' and is
|
|
;; what every hand-written call in the tree does:
|
|
;;
|
|
;; (rl/draw-rectangle-lines-ex
|
|
;; (rl/Rectangle {.x 0.0 .y 0.0})
|
|
;; (f32 2.0) (rl/get-color 0x303030FF))
|
|
(+ (current-column) lisp-body-indent -1)))))
|
|
|
|
(defun flan--clause-form-p ()
|
|
"Non-nil if the form at point is a clause: `(name [params] body…)'.
|
|
Point is just after the open paren, on the head.
|
|
|
|
This is `defn' with the name left off, and it is how `handler-bind',
|
|
`handler-case' and `restart-case' all write their clauses. Their heads are
|
|
condition classes and restart names — things a program invents — so no table
|
|
here could ever list them; the shape is what can be recognised."
|
|
(save-excursion
|
|
(ignore-errors
|
|
(flan--forward-sexp 1) ; over the head
|
|
(skip-chars-forward " \t\n\r,")
|
|
(eq (char-after) ?\[))))
|
|
|
|
(defun flan--count-indent (method indent-point last-sexp head-column)
|
|
"Indent inside a form whose first METHOD arguments are special.
|
|
INDENT-POINT, LAST-SEXP and HEAD-COLUMN are as in `flan-indent-function';
|
|
point is just after the open paren."
|
|
(let ((pos -1))
|
|
(condition-case nil
|
|
(while (and (<= (point) indent-point) (not (eobp)))
|
|
(flan--forward-sexp 1)
|
|
(setq pos (1+ pos)))
|
|
;; Past the last sexp in the form: count as if one more were here, which
|
|
;; is what indenting an empty line at the end of a form means.
|
|
(scan-error (setq pos (1+ pos))))
|
|
(cond
|
|
;; The first argument that is body rather than special.
|
|
((= pos (1+ method)) (+ lisp-body-indent head-column))
|
|
;; Further body arguments line up with the one above.
|
|
((> pos (1+ method)) (flan--normal-indent last-sexp))
|
|
;; Still in the special arguments. Clojure indents these to twice the
|
|
;; body indent so they cannot be mistaken for body; Flan uses one, because
|
|
;; the corpus is written that way throughout —
|
|
;;
|
|
;; (handler-bind
|
|
;; [(StorageExhausted [c] …)]
|
|
;; (load-all))
|
|
;;
|
|
;; — and there is nothing to confuse: the special arguments come first, so
|
|
;; a reader never has to tell them apart by column.
|
|
(t (+ lisp-body-indent head-column)))))
|
|
|
|
(defun flan-indent-function (indent-point state)
|
|
"Indent a line inside a Flan form.
|
|
INDENT-POINT and STATE are as for `lisp-indent-function'; the spec for the
|
|
enclosing form comes from `flan-indent-specs'. Returns nil to leave the
|
|
decision to `calculate-lisp-indent'."
|
|
(goto-char (elt state 1))
|
|
(if (flan--data-form-p)
|
|
;; A vector, a map, or a head that is not a symbol: align under the
|
|
;; first element.
|
|
(1+ (current-column))
|
|
(forward-char 1)
|
|
(let* ((name (thing-at-point 'symbol))
|
|
(method (flan--indent-spec name))
|
|
(last-sexp calculate-lisp-indent-last-sexp)
|
|
(head-column (1- (current-column))))
|
|
(cond
|
|
((integerp method)
|
|
(flan--count-indent method indent-point last-sexp head-column))
|
|
((eq method :defn) (+ lisp-body-indent head-column))
|
|
;; No spec. Anything else spelled `def…' is a definition and indents
|
|
;; like one, which covers `defstruct', `defdata', `defunion',
|
|
;; `defenum', `defvar', `defconst' and `defalias' without naming
|
|
;; them.
|
|
((and name (string-match-p "\\`def" name))
|
|
(+ lisp-body-indent head-column))
|
|
;; A clause: `(name [params] body…)'. `handler-bind', `handler-case'
|
|
;; and `restart-case' all write their clauses this way, and the head is
|
|
;; a condition class or a restart name — something the program invented,
|
|
;; so it can never be in a table here. What can be recognised is the
|
|
;; *shape*, which is `defn' with the name left off: a parameter vector
|
|
;; where the first argument goes, and a body after it.
|
|
;;
|
|
;; `clojure-mode' reaches the same clauses by backtracking out to the
|
|
;; enclosing form and reading a nested spec off it. That machinery buys
|
|
;; generality this language has no other use for — these three forms are
|
|
;; the whole of it — and the shape is unambiguous on its own.
|
|
((flan--clause-form-p) (flan--count-indent 1 indent-point last-sexp
|
|
head-column))
|
|
(t (flan--normal-indent last-sexp))))))
|
|
|
|
;;;###autoload
|
|
(add-to-list 'auto-mode-alist '("\\.flan\\'" . flan-mode))
|
|
|
|
(provide 'flan-mode)
|
|
;;; flan-mode.el ends here
|