;;; 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