| 1 | ;; simp-hare-mode, a simple hare major mode based on simpc, License: GPL -*- lexical-binding: t; -*- |
| 2 | (require 'subr-x) |
| 3 | |
| 4 | (defvar simp-hare-mode-syntax-table |
| 5 | (let ((table (make-syntax-table))) |
| 6 | ;; C/C++ style comments |
| 7 | (modify-syntax-entry ?/ ". 124b" table) |
| 8 | (modify-syntax-entry ?* ". 23" table) |
| 9 | (modify-syntax-entry ?\n "> b" table) |
| 10 | ;; Preprocessor stuff? |
| 11 | (modify-syntax-entry ?# "." table) |
| 12 | ;; Chars are the same as strings |
| 13 | (modify-syntax-entry ?' "\"" table) |
| 14 | ;; Treat <> as punctuation (needed to highlight C++ keywords |
| 15 | ;; properly in template syntax) |
| 16 | (modify-syntax-entry ?< "." table) |
| 17 | (modify-syntax-entry ?> "." table) |
| 18 | (modify-syntax-entry ?& "." table) |
| 19 | (modify-syntax-entry ?% "." table) |
| 20 | table)) |
| 21 | |
| 22 | (defun simp-hare-types () |
| 23 | '("abort" "align" "alloc" "append" "as" "assert" "bool" "break" |
| 24 | "case" "const" "continue" "def" "defer" "delete" |
| 25 | "done" "else" "export" "false" "fn" "for" "free" "if" "insert" |
| 26 | "is" "len" "let" "match" "never" "nullable" |
| 27 | "offset" "return" "size" "uintptr" "switch" "use" "vaarg" "vaend" |
| 28 | "valist" "vastart" "yield" "_")) |
| 29 | |
| 30 | (defun simp-hare-keywords () |
| 31 | '("f32" "f64" "enum" "i16" "i32" "i64" "i8" "int" "null" "nomem" "opaque" "static" "rune" |
| 32 | "str" "struct" "true" "type" "u16" "u32" "u64" "u8" "uint" "void" "union")) |
| 33 | |
| 34 | (defun simp-hare-font-lock-keywords () |
| 35 | (list |
| 36 | `("# *\\(warn\\|error\\)" . font-lock-warning-face) |
| 37 | `("# *[#a-zA-Z0-9_]+" . font-lock-preprocessor-face) |
| 38 | `("# *include\\(?:_next\\)?\\s-+\\(\\(<\\|\"\\).*\\(>\\|\"\\)\\)" . (1 font-lock-string-face)) |
| 39 | `("\\(?:enum\\|struct\\)\\s-+\\([a-zA-Z0-9_]+\\)" . (1 font-lock-type-face)) |
| 40 | `(,(regexp-opt (simp-hare-keywords) 'symbols) . font-lock-keyword-face) |
| 41 | `(,(regexp-opt (simp-hare-types) 'symbols) . font-lock-type-face))) |
| 42 | |
| 43 | (defun simp-hare--previous-non-empty-line () |
| 44 | "Returns either NIL when there is no such line or a pair (line . indentation)" |
| 45 | (save-excursion |
| 46 | ;; If you are on the first line, but not at the beginning of buffer (BOB) the `(bobp)` |
| 47 | ;; function does not return `t`. So we have to move to the beginning of the line first. |
| 48 | ;; TODO: feel free to suggest a better approach for checking BOB here. |
| 49 | (move-beginning-of-line nil) |
| 50 | (if (bobp) |
| 51 | ;; If you are standing at the BOB, you by definition don't have a previous non-empty line. |
| 52 | nil |
| 53 | ;; Moving one line backwards because the current line is by definition is not |
| 54 | ;; the previous non-empty line. |
| 55 | (forward-line -1) |
| 56 | ;; Keep moving backwards until we hit BOB or a non-empty line. |
| 57 | (while (and (not (bobp)) |
| 58 | (string-empty-p |
| 59 | (string-trim-right |
| 60 | (thing-at-point 'line t)))) |
| 61 | (forward-line -1)) |
| 62 | |
| 63 | (if (string-empty-p |
| 64 | (string-trim-right |
| 65 | (thing-at-point 'line t))) |
| 66 | ;; If after moving backwards for this long we still look at an empty |
| 67 | ;; line we by definition didn't find the previous non-empty line. |
| 68 | nil |
| 69 | ;; We found the previous non-empty line! |
| 70 | (cons (thing-at-point 'line t) |
| 71 | (current-indentation)))))) |
| 72 | |
| 73 | (defun simp-hare--desired-indentation () |
| 74 | (let ((prev (simp-hare--previous-non-empty-line))) |
| 75 | (if (not prev) |
| 76 | (current-indentation) |
| 77 | (let ((indent-len 8) |
| 78 | (cur-line (string-trim-right (thing-at-point 'line t))) |
| 79 | (prev-line (string-trim-right (car prev))) |
| 80 | (prev-indent (cdr prev))) |
| 81 | (cond |
| 82 | ((string-match-p "^\\s-*switch\\s-*(.+)" prev-line) |
| 83 | prev-indent) |
| 84 | ((string-match-p "^\\s-*match\\s-*(.+)" prev-line) |
| 85 | prev-indent) |
| 86 | ((and (string-suffix-p "{" prev-line) |
| 87 | (string-prefix-p "}" (string-trim-left cur-line))) |
| 88 | prev-indent) |
| 89 | ((string-suffix-p "{" prev-line) |
| 90 | (+ prev-indent indent-len)) |
| 91 | ((string-prefix-p "}" (string-trim-left cur-line)) |
| 92 | (max (- prev-indent indent-len) 0)) |
| 93 | ((string-suffix-p ":" prev-line) |
| 94 | (if (string-suffix-p ":" cur-line) |
| 95 | prev-indent |
| 96 | (+ prev-indent indent-len))) |
| 97 | ((string-suffix-p ":" cur-line) |
| 98 | (max (- prev-indent indent-len) 0)) |
| 99 | ((string-suffix-p "=>" prev-line) |
| 100 | (if (string-suffix-p "=>" cur-line) |
| 101 | prev-indent |
| 102 | (+ prev-indent indent-len))) |
| 103 | ((string-suffix-p "=>" cur-line) |
| 104 | (max (- prev-indent indent-len) 0)) |
| 105 | (t prev-indent)))))) |
| 106 | |
| 107 | ;;; TODO: customizable indentation (amount of spaces, tabs, etc) |
| 108 | (defun simp-hare-indent-line () |
| 109 | (interactive) |
| 110 | (when (not (bobp)) |
| 111 | (let* ((desired-indentation |
| 112 | (simp-hare--desired-indentation)) |
| 113 | (n (max (- (current-column) (current-indentation)) 0))) |
| 114 | (indent-line-to desired-indentation) |
| 115 | (forward-char n)))) |
| 116 | |
| 117 | (define-derived-mode simp-hare-mode prog-mode "Simple Hare" |
| 118 | "Simple major mode for editing Hare files based on simpc." |
| 119 | :syntax-table simp-hare-mode-syntax-table |
| 120 | (setq-local font-lock-defaults '(simp-hare-font-lock-keywords)) |
| 121 | (setq-local indent-line-function 'simp-hare-indent-line) |
| 122 | (setq-local comment-start "// ")) |
| 123 | |
| 124 | (provide 'simp-hare-mode) |