|
| 1 | +;;; lbd-mode.el --- Major mode for Lambda-Discipline Language -*- lexical-binding: t; -*- |
| 2 | +;; |
| 3 | +;; Author: NinthCircle |
| 4 | +;; Version: 0.1 |
| 5 | +;; Keywords: languages |
| 6 | +;; URL: https://gitlab.com/ninthcircle/lambda-discipline/-/tree/master/editor-plugins/emacs/lbd-mode.el |
| 7 | + |
| 8 | +(defvar lbd-mode-hook nil) |
| 9 | + |
| 10 | +(defvar lbd-mode-map |
| 11 | + (let ((map (make-sparse-keymap))) |
| 12 | + map) |
| 13 | + "Keymap for lbd major mode") |
| 14 | + |
| 15 | +;; Syntax table |
| 16 | +(defvar lbd-mode-syntax-table |
| 17 | + (let ((st (make-syntax-table))) |
| 18 | + ;; Comment syntax: “--” starts a comment, newline ends it |
| 19 | + (modify-syntax-entry ?- ". 12" st) |
| 20 | + (modify-syntax-entry ?\n ">" st) |
| 21 | + |
| 22 | + ;; Parentheses |
| 23 | + (modify-syntax-entry ?\( "(" st) |
| 24 | + (modify-syntax-entry ?\) ")" st) |
| 25 | + |
| 26 | + ;; You can tweak these if you want string/char support later |
| 27 | + (modify-syntax-entry ?\" "\"" st) |
| 28 | + (modify-syntax-entry ?' "w" st) |
| 29 | + |
| 30 | + st) |
| 31 | + "Syntax table for lbd-mode.") |
| 32 | + |
| 33 | +;; Comment settings |
| 34 | +(setq-local comment-start "-- ") |
| 35 | +(setq-local comment-end "") |
| 36 | + |
| 37 | +;; Font lock |
| 38 | +(defconst lbd--keywords |
| 39 | + '("true" "false" "if_then" "pair" "first" "second" "use") |
| 40 | + "Language keywords for lbd-mode.") |
| 41 | + |
| 42 | +(defvar lbd-font-lock-keywords |
| 43 | + (let* ((kw (regexp-opt lbd--keywords 'words)) |
| 44 | + ;; types are capitalized identifiers like Any Bool Float |
| 45 | + (type-re "\\b\\([A-Z][A-Za-z0-9_]*\\)\\b") |
| 46 | + ;; arrows |
| 47 | + (arrow-re "->") |
| 48 | + ;; lambda/backslash `\` and the dot that follows the argument list |
| 49 | + (lambda-re "\\\\[[:alnum:]_]+:\\s-*\\([A-Za-z0-9_<>]+\\)\\.\\|\\\\") |
| 50 | + ;; top-level defs: name : type = |
| 51 | + (def-re "^\\s-*\\([a-zA-Z_][a-zA-Z0-9_?']*\\)\\s-*:") |
| 52 | + ) |
| 53 | + `( |
| 54 | + (,kw . font-lock-keyword-face) |
| 55 | + (,type-re 1 font-lock-type-face) |
| 56 | + (,arrow-re . font-lock-builtin-face) |
| 57 | + ("\\\\" . font-lock-builtin-face) ;; the backslash lambda |
| 58 | + ("\\." . font-lock-constant-face) ;; dots as separators |
| 59 | + ;; highlight top-level names like `name:` at bol |
| 60 | + (,def-re 1 font-lock-function-name-face) |
| 61 | + )) |
| 62 | + "Font lock patterns for lbd-mode.") |
| 63 | + |
| 64 | +;; Indentation |
| 65 | +(defun lbd--calculate-indent () |
| 66 | + "Calculate indentation for current line in `lbd-mode'. |
| 67 | +Returns number of spaces to indent to. |
| 68 | +This implementation uses parenthesis depth as a baseline and adds |
| 69 | +4 spaces for continued lines after a dot or after a lambda argument list." |
| 70 | + (save-excursion |
| 71 | + (beginning-of-line) |
| 72 | + (let* ((parse (syntax-ppss)) |
| 73 | + (paren-depth (nth 0 parse)) |
| 74 | + indent) |
| 75 | + (cond |
| 76 | + ;; If inside parens, align with the opening paren plus two spaces |
| 77 | + ((> paren-depth 0) |
| 78 | + (goto-char (nth 1 parse)) |
| 79 | + (setq indent (+ (current-column) 2))) |
| 80 | + ;; If previous non-blank line ends with '.' or is a lambda start, indent |
| 81 | + (t |
| 82 | + (save-excursion |
| 83 | + (forward-line -1) |
| 84 | + (while (and (not (bobp)) (looking-at "^\\s-*$")) |
| 85 | + (forward-line -1)) |
| 86 | + (end-of-line) |
| 87 | + (cond |
| 88 | + ((looking-back "\\\\.*\\.\\s-*" (line-beginning-position)) |
| 89 | + (setq indent (+ (current-indentation) 4))) |
| 90 | + ((looking-back "\\\\" (line-beginning-position)) |
| 91 | + (setq indent (+ (current-indentation) 4))) |
| 92 | + ((looking-back "\\.\\s-*" (line-beginning-position)) |
| 93 | + (setq indent (+ (current-indentation) 4))) |
| 94 | + (t (setq indent 0)))))) |
| 95 | + (max indent 0)))) |
| 96 | + |
| 97 | +(defun lbd-indent-line () |
| 98 | + "Indent current line as lbd code." |
| 99 | + (interactive) |
| 100 | + (let ((indent (lbd--calculate-indent)) |
| 101 | + (offset (- (current-column) (current-indentation)))) |
| 102 | + (if indent |
| 103 | + (progn |
| 104 | + (indent-line-to indent) |
| 105 | + (when (> offset 0) (forward-char offset))) |
| 106 | + (indent-line-to 0)))) |
| 107 | + |
| 108 | +;; Imenu support |
| 109 | +(defvar lbd-imenu-generic-expression |
| 110 | + '(( "Definitions" "^\\s-*\\([a-zA-Z_][a-zA-Z0-9_?']*\\)\\s-*:" 1)) |
| 111 | + "Imenu expression for lbd-mode to index top-level definitions.") |
| 112 | + |
| 113 | +;;;###autoload |
| 114 | +(define-derived-mode lbd-mode prog-mode "LBD" |
| 115 | + "Major mode for editing Lambda-Discipline-like files." |
| 116 | + :syntax-table lbd-mode-syntax-table |
| 117 | + (setq-local font-lock-defaults '(lbd-font-lock-keywords)) |
| 118 | + ;; comment style |
| 119 | + (setq-local comment-start "-- ") |
| 120 | + (setq-local comment-end "") |
| 121 | + ;; indentation |
| 122 | + (setq-local indent-line-function #'lbd-indent-line) |
| 123 | + ;; imenu |
| 124 | + (setq-local imenu-generic-expression lbd-imenu-generic-expression)) |
| 125 | + |
| 126 | +;;;###autoload |
| 127 | +(add-to-list 'auto-mode-alist '("\\.lbd\\'" . lbd-mode)) |
| 128 | +(add-to-list 'auto-mode-alist '("\\.lambda\\'" . lbd-mode)) |
| 129 | + |
| 130 | +(provide 'lbd-mode) |
| 131 | +;;; lbd-mode.el ends here |
0 commit comments