flan-repl.el is a comint buffer whose every line goes through the same eval-expr request C-x C-e uses - no new protocol, no compiler support. Deriving from comint rather than hand-rolling a prompt is the same call as deriving flan-mode from lisp-mode: history, the input ring and kill/yank already exist and are not worth rewriting. There is no subprocess behind it; the "process" is a stub comint needs in order to have a prompt. It is program-scoped: a name typed at the prompt resolves against the running program's top-level namespace, so in sand you write sim/settle. A buffer visiting a package's file gets the alias applied for it because the file says which package it belongs to, and a prompt has no file to derive one from. RET on a half-typed form opens a line instead of sending it, with balance checked through the Flan syntax table so a paren inside a string does not count. A value and the program's output are different things and arrive by different routes: the value is the result of the request and appears at the prompt, while anything printed rides along on the same reply into *flan-output*. Showing them in one place would be convenient and wrong, so there is a test for the separation - and it caught a real bug. The renderer's Unit case emitted () with no evaluation at all, so (print-line "x"), the most ordinary thing anyone types at a prompt, answered while nothing happened. A Unit expression is almost always a call made for its effect; it is evaluated and then reported.
119 lines
4.7 KiB
EmacsLisp
119 lines
4.7 KiB
EmacsLisp
;;; flan-mode.el --- Major mode for Flan -*- lexical-binding: t; -*-
|
|
|
|
;; Derived from lisp-mode, which is most of the work: Flan is s-expressions, so
|
|
;; sexp motion, paren matching, `beginning-of-defun' and indentation all
|
|
;; already do the right thing. What is left is what the language actually adds
|
|
;; — its own literals and its own set of forms that indent like a body.
|
|
|
|
;;; Code:
|
|
|
|
(require 'lisp-mode)
|
|
|
|
;; The keymap binds them; loading the client is what defines them, and a user
|
|
;; may well edit Flan without ever connecting to a running program.
|
|
(declare-function flan-eval-defun "flan-dev")
|
|
(declare-function flan-eval-buffer "flan-dev")
|
|
(declare-function flan-eval-last-sexp "flan-dev")
|
|
(declare-function flan-connect "flan-dev")
|
|
(declare-function flan-disconnect "flan-dev")
|
|
(declare-function flan-describe "flan-dev")
|
|
(declare-function flan-show-output "flan-dev")
|
|
(declare-function flan-repl "flan-repl")
|
|
|
|
(defgroup flan nil
|
|
"Editing and evaluating Flan."
|
|
:group 'languages
|
|
:prefix "flan-")
|
|
|
|
(defconst flan--definers
|
|
'("defn" "defvar" "defconst" "defstruct" "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")
|
|
"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)
|
|
;; 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'.")
|
|
|
|
(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-dev.el, so the client loads on first use.
|
|
(define-key map (kbd "C-c C-c") #'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)
|
|
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)
|
|
(setq-local lisp-indent-function #'flan-indent-function)
|
|
(setq-local outline-regexp ";;;;+[ \t]*"))
|
|
|
|
(defun flan-indent-function (indent-point state)
|
|
"Indent like Lisp, with Flan's body forms as special forms.
|
|
INDENT-POINT and STATE are as for `lisp-indent-function'."
|
|
(let ((open (elt state 1)))
|
|
(or (and open
|
|
(save-excursion
|
|
(goto-char (1+ open))
|
|
(let ((head (and (looking-at "\\(\\sw\\|\\s_\\)+")
|
|
(match-string 0))))
|
|
(when (member head '("defn" "let" "if" "while" "until"
|
|
"dotimes" "match" "do" "loop" "defer"))
|
|
(+ (current-column) 1)))))
|
|
(lisp-indent-function indent-point state))))
|
|
|
|
;;;###autoload
|
|
(add-to-list 'auto-mode-alist '("\\.flan\\'" . flan-mode))
|
|
|
|
(provide 'flan-mode)
|
|
;;; flan-mode.el ends here
|