Austin Schuh | dace2a6 | 2020-08-18 10:56:48 -0700 | [diff] [blame^] | 1 | ;;; gmpasm-mode.el -- GNU MP asm and m4 editing mode. |
| 2 | |
| 3 | |
| 4 | ;; Copyright 1999-2002 Free Software Foundation, Inc. |
| 5 | |
| 6 | ;; This file is part of the GNU MP Library. |
| 7 | ;; |
| 8 | ;; The GNU MP Library is free software; you can redistribute it and/or modify |
| 9 | ;; it under the terms of either: |
| 10 | ;; |
| 11 | ;; * the GNU Lesser General Public License as published by the Free |
| 12 | ;; Software Foundation; either version 3 of the License, or (at your |
| 13 | ;; option) any later version. |
| 14 | ;; |
| 15 | ;; or |
| 16 | ;; |
| 17 | ;; * the GNU General Public License as published by the Free Software |
| 18 | ;; Foundation; either version 2 of the License, or (at your option) any |
| 19 | ;; later version. |
| 20 | ;; |
| 21 | ;; or both in parallel, as here. |
| 22 | ;; |
| 23 | ;; The GNU MP Library is distributed in the hope that it will be useful, but |
| 24 | ;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| 25 | ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 26 | ;; for more details. |
| 27 | ;; |
| 28 | ;; You should have received copies of the GNU General Public License and the |
| 29 | ;; GNU Lesser General Public License along with the GNU MP Library. If not, |
| 30 | ;; see https://www.gnu.org/licenses/. |
| 31 | |
| 32 | |
| 33 | ;;; Commentary: |
| 34 | ;; |
| 35 | ;; gmpasm-mode is a major mode for editing m4 processed assembler code and |
| 36 | ;; m4 macro files in GMP. It's similar to m4-mode, but has a number of |
| 37 | ;; settings better suited to GMP. |
| 38 | ;; |
| 39 | ;; |
| 40 | ;; Install |
| 41 | ;; ------- |
| 42 | ;; |
| 43 | ;; To make M-x gmpasm-mode available, put gmpasm-mode.el somewhere in your |
| 44 | ;; load-path and the following in your .emacs |
| 45 | ;; |
| 46 | ;; (autoload 'gmpasm-mode "gmpasm-mode" nil t) |
| 47 | ;; |
| 48 | ;; To use gmpasm-mode automatically on all .asm and .m4 files, put the |
| 49 | ;; following in your .emacs |
| 50 | ;; |
| 51 | ;; (add-to-list 'auto-mode-alist '("\\.asm\\'" . gmpasm-mode)) |
| 52 | ;; (add-to-list 'auto-mode-alist '("\\.m4\\'" . gmpasm-mode)) |
| 53 | ;; |
| 54 | ;; To have gmpasm-mode only on gmp files, try instead something like the |
| 55 | ;; following, which uses it only in a directory starting with "gmp", or a |
| 56 | ;; sub-directory of such. |
| 57 | ;; |
| 58 | ;; (add-to-list 'auto-mode-alist |
| 59 | ;; '("/gmp.*/.*\\.\\(asm\\|m4\\)\\'" . gmpasm-mode)) |
| 60 | ;; |
| 61 | ;; Byte compiling will slightly speed up loading. If you want a docstring |
| 62 | ;; in the autoload you can use M-x update-file-autoloads if you set it up |
| 63 | ;; right. |
| 64 | ;; |
| 65 | ;; |
| 66 | ;; Emacsen |
| 67 | ;; ------- |
| 68 | ;; |
| 69 | ;; GNU Emacs 20.x, 21.x and XEmacs 20.x all work well. GNU Emacs 19.x |
| 70 | ;; should work if replacements for the various 20.x-isms are available, |
| 71 | ;; though comment-region with "C" doesn't do the right thing. |
| 72 | |
| 73 | |
| 74 | ;;; Code: |
| 75 | |
| 76 | (defgroup gmpasm nil |
| 77 | "GNU MP m4 and asm editing." |
| 78 | :prefix "gmpasm-" |
| 79 | :group 'languages) |
| 80 | |
| 81 | (defcustom gmpasm-mode-hook nil |
| 82 | "*Hook called by `gmpasm-mode'." |
| 83 | :type 'hook |
| 84 | :group 'gmpasm) |
| 85 | |
| 86 | (defcustom gmpasm-comment-start-regexp "\\([#;!@*|C]\\|//\\)" |
| 87 | "*Regexp matching possible comment styles. |
| 88 | See `gmpasm-mode' docstring for how this is used. |
| 89 | |
| 90 | Commenting styles within GMP include |
| 91 | # - alpha, i386, i960, vax, traditional unix |
| 92 | ; - a29k, clipper, hppa, m88k, ppc |
| 93 | ! - sh, sparc, z8000 |
| 94 | | - m68k |
| 95 | @ - arm |
| 96 | * - cray |
| 97 | C - GMP m4, see mpn/asm-defs.m4 |
| 98 | // - ia64" |
| 99 | :type 'regexp |
| 100 | :group 'gmpasm) |
| 101 | |
| 102 | |
| 103 | (defun gmpasm-add-to-list-second (list-var element) |
| 104 | "(gmpasm-add-to-list-second LIST-VAR ELEMENT) |
| 105 | |
| 106 | Add ELEMENT to LIST-VAR as the second element in the list, if it isn't |
| 107 | already in the list. If LIST-VAR is nil, then ELEMENT is just added as the |
| 108 | sole element in the list. |
| 109 | |
| 110 | This is like `add-to-list', but it puts the new value second in the list. |
| 111 | |
| 112 | The first cons cell is copied rather than changed in-place, so references to |
| 113 | the list elsewhere won't be affected." |
| 114 | |
| 115 | (if (member element (symbol-value list-var)) |
| 116 | (symbol-value list-var) |
| 117 | (set list-var |
| 118 | (if (symbol-value list-var) |
| 119 | (cons (car (symbol-value list-var)) |
| 120 | (cons element |
| 121 | (cdr (symbol-value list-var)))) |
| 122 | (list element))))) |
| 123 | |
| 124 | |
| 125 | (defun gmpasm-remove-from-list (list-var element) |
| 126 | "(gmpasm-remove-from-list LIST-VAR ELEMENT) |
| 127 | |
| 128 | Remove ELEMENT from LIST-VAR, using `copy-sequence' and `delete'. |
| 129 | This is vaguely like `add-to-list', but the element is removed from the list. |
| 130 | The list is copied rather than changed in-place, so references to it elsewhere |
| 131 | aren't affected." |
| 132 | |
| 133 | ;; Only the portion of the list up to the removed element needs to be |
| 134 | ;; copied, but there's no need to bother arranging that, since this function |
| 135 | ;; is only used for a couple of initializations. |
| 136 | |
| 137 | (set list-var (delete element (copy-sequence (symbol-value list-var))))) |
| 138 | |
| 139 | |
| 140 | (defvar gmpasm-mode-map |
| 141 | (let ((map (make-sparse-keymap))) |
| 142 | |
| 143 | ;; assembler and dnl commenting |
| 144 | (define-key map "\C-c\C-c" 'comment-region) |
| 145 | (define-key map "\C-c\C-d" 'gmpasm-comment-region-dnl) |
| 146 | |
| 147 | ;; kill an M-x compile, since it's not hard to put m4 into an infinite |
| 148 | ;; loop |
| 149 | (define-key map "\C-c\C-k" 'kill-compilation) |
| 150 | |
| 151 | map) |
| 152 | "Keymap for `gmpasm-mode'.") |
| 153 | |
| 154 | |
| 155 | (defvar gmpasm-mode-syntax-table |
| 156 | (let ((table (make-syntax-table))) |
| 157 | ;; underscore left as a symbol char, like C mode |
| 158 | |
| 159 | ;; m4 quotes |
| 160 | (modify-syntax-entry ?` "('" table) |
| 161 | (modify-syntax-entry ?' ")`" table) |
| 162 | |
| 163 | table) |
| 164 | "Syntax table used in `gmpasm-mode'. |
| 165 | |
| 166 | '#' and '\n' aren't set as comment syntax. In m4 these are a comment |
| 167 | outside quotes, but not inside. Omitting a syntax entry ensures that when |
| 168 | inside quotes emacs treats parentheses and apostrophes the same way that m4 |
| 169 | does. When outside quotes this is not quite right, but having it right when |
| 170 | nesting expressions is more important. |
| 171 | |
| 172 | '*', '!' or '|' aren't setup as comment syntax either, on CPUs which use |
| 173 | these for comments. The GMP macro setups don't set them in m4 changecom(), |
| 174 | since that prevents them being used in eval() expressions, and on that basis |
| 175 | they don't change the way quotes and parentheses are treated by m4 and |
| 176 | should be treated by emacs.") |
| 177 | |
| 178 | |
| 179 | (defvar gmpasm-font-lock-keywords |
| 180 | (eval-when-compile |
| 181 | (list |
| 182 | (cons |
| 183 | (concat |
| 184 | "\\b" |
| 185 | (regexp-opt |
| 186 | '("deflit" "defreg" "defframe" "defframe_pushl" |
| 187 | "define_not_for_expansion" |
| 188 | "m4_error" "m4_warning" |
| 189 | "ASM_START" "ASM_END" |
| 190 | "PROLOGUE" "PROLOGUE_GP" "MULFUNC_PROLOGUE" "EPILOGUE" |
| 191 | "DATASTART" "DATAEND" |
| 192 | "forloop" |
| 193 | "TEXT" "DATA" "ALIGN" "W32" "FLOAT64" |
| 194 | "builtin" "changecom" "changequote" "changeword" "debugfile" |
| 195 | "debugmode" "decr" "define" "defn" "divert" "divnum" "dumpdef" |
| 196 | "errprint" "esyscmd" "eval" "__file__" "format" "gnu" "ifdef" |
| 197 | "ifelse" "include" "incr" "index" "indir" "len" "__line__" |
| 198 | "m4exit" "m4wrap" "maketemp" "patsubst" "popdef" "pushdef" |
| 199 | "regexp" "shift" "sinclude" "substr" "syscmd" "sysval" |
| 200 | "traceoff" "traceon" "translit" "undefine" "undivert" "unix") |
| 201 | t) |
| 202 | "\\b") 'font-lock-keyword-face))) |
| 203 | |
| 204 | "`font-lock-keywords' for `gmpasm-mode'. |
| 205 | |
| 206 | The keywords are m4 builtins and some of the GMP macros used in asm files. |
| 207 | L doesn't look good fontified, so it's omitted. |
| 208 | |
| 209 | The right assembler comment regexp is added dynamically buffer-local (with |
| 210 | dnl too).") |
| 211 | |
| 212 | |
| 213 | ;; Initialized if gmpasm-mode finds filladapt loaded. |
| 214 | (defvar gmpasm-filladapt-token-table nil |
| 215 | "Filladapt token table used in `gmpasm-mode'.") |
| 216 | (defvar gmpasm-filladapt-token-match-table nil |
| 217 | "Filladapt token match table used in `gmpasm-mode'.") |
| 218 | (defvar gmpasm-filladapt-token-conversion-table nil |
| 219 | "Filladapt token conversion table used in `gmpasm-mode'.") |
| 220 | |
| 221 | |
| 222 | ;;;###autoload |
| 223 | (defun gmpasm-mode () |
| 224 | "A major mode for editing GNU MP asm and m4 files. |
| 225 | |
| 226 | \\{gmpasm-mode-map} |
| 227 | `comment-start' and `comment-end' are set buffer-local to assembler |
| 228 | commenting appropriate for the CPU by looking for something matching |
| 229 | `gmpasm-comment-start-regexp' at the start of a line, or \"#\" is used if |
| 230 | there's no match (if \"#\" isn't what you want, type in a desired comment |
| 231 | and do \\[gmpasm-mode] to reinitialize). |
| 232 | |
| 233 | `adaptive-fill-regexp' is set buffer-local to the standard regexp with |
| 234 | `comment-start' and dnl added. If filladapt.el has been loaded it similarly |
| 235 | gets `comment-start' and dnl added as buffer-local fill prefixes. |
| 236 | |
| 237 | Font locking has the m4 builtins, some of the GMP macros, m4 dnl commenting, |
| 238 | and assembler commenting (based on the `comment-start' determined). |
| 239 | |
| 240 | Note that `gmpasm-comment-start-regexp' is only matched as a whole word, so |
| 241 | the `C' in it is only matched as a whole word, not on something that happens |
| 242 | to start with `C'. Also it's only the particular `comment-start' determined |
| 243 | that's added for filling etc, not the whole `gmpasm-comment-start-regexp'. |
| 244 | |
| 245 | `gmpasm-mode-hook' is run after initializations are complete." |
| 246 | |
| 247 | (interactive) |
| 248 | (kill-all-local-variables) |
| 249 | (setq major-mode 'gmpasm-mode |
| 250 | mode-name "gmpasm") |
| 251 | (use-local-map gmpasm-mode-map) |
| 252 | (set-syntax-table gmpasm-mode-syntax-table) |
| 253 | (setq fill-column 76) |
| 254 | |
| 255 | ;; Short instructions might fit with 32, but anything with labels or |
| 256 | ;; expressions soon needs the comments pushed out to column 40. |
| 257 | (setq comment-column 40) |
| 258 | |
| 259 | ;; Don't want to find out the hard way which dumb assemblers don't like a |
| 260 | ;; missing final newline. |
| 261 | (set (make-local-variable 'require-final-newline) t) |
| 262 | |
| 263 | ;; The first match of gmpasm-comment-start-regexp at the start of a line |
| 264 | ;; determines comment-start, or "#" if no match. |
| 265 | (set (make-local-variable 'comment-start) |
| 266 | (save-excursion |
| 267 | (goto-char (point-min)) |
| 268 | (if (re-search-forward |
| 269 | (concat "^\\(" gmpasm-comment-start-regexp "\\)\\(\\s-\\|$\\)") |
| 270 | nil t) |
| 271 | (match-string 1) |
| 272 | "#"))) |
| 273 | (set (make-local-variable 'comment-end) "") |
| 274 | |
| 275 | ;; If comment-start ends in an alphanumeric then \b is used to match it |
| 276 | ;; only as a separate word. The test is for an alphanumeric rather than |
| 277 | ;; \w since we might try # or ! as \w characters but without wanting \b on |
| 278 | ;; them. |
| 279 | (let ((comment-regexp |
| 280 | (concat (regexp-quote comment-start) |
| 281 | (if (string-match "[a-zA-Z0-9]\\'" comment-start) "\\b")))) |
| 282 | |
| 283 | ;; Whitespace is required before a comment-start so m4 $# doesn't match |
| 284 | ;; when comment-start is "#". |
| 285 | (set (make-local-variable 'comment-start-skip) |
| 286 | (concat "\\(^\\|\\s-\\)\\(\\<dnl\\>\\|" comment-regexp "\\)[ \t]*")) |
| 287 | |
| 288 | ;; Comment fontification based on comment-start, and always with dnl. |
| 289 | ;; Same treatment of a space before "#" as in comment-start-skip, but |
| 290 | ;; don't fontify that space. |
| 291 | (add-to-list (make-local-variable 'gmpasm-font-lock-keywords) |
| 292 | (list (concat "\\(^\\|\\s-\\)\\(\\(\\<dnl\\>\\|" |
| 293 | comment-regexp |
| 294 | "\\).*$\\)") |
| 295 | 2 'font-lock-comment-face)) |
| 296 | |
| 297 | (set (make-local-variable 'font-lock-defaults) |
| 298 | '(gmpasm-font-lock-keywords |
| 299 | t ; no syntactic fontification (of strings etc) |
| 300 | nil ; no case-fold |
| 301 | ((?_ . "w")) ; _ part of a word while fontifying |
| 302 | )) |
| 303 | |
| 304 | ;; Paragraphs are separated by blank lines, or lines with only dnl or |
| 305 | ;; comment-start. |
| 306 | (set (make-local-variable 'paragraph-separate) |
| 307 | (concat "[ \t\f]*\\(\\(" comment-regexp "\\|dnl\\)[ \t]*\\)*$")) |
| 308 | (set (make-local-variable 'paragraph-start) |
| 309 | (concat "\f\\|" paragraph-separate)) |
| 310 | |
| 311 | ;; Some sort of "def...(" m4 define, possibly with ` for quoting. |
| 312 | ;; Could do something with PROLOGUE here, but in GMP the filename is |
| 313 | ;; enough, it's not normally necessary to say the function name. |
| 314 | (set (make-local-variable 'add-log-current-defun-header-regexp) |
| 315 | "^def[a-z0-9_]+(`?\\([a-zA-Z0-9_]+\\)") |
| 316 | |
| 317 | ;; Adaptive fill gets dnl and comment-start as comment style prefixes on |
| 318 | ;; top of the standard regexp (which has # and ; already actually). |
| 319 | (set (make-local-variable 'adaptive-fill-regexp) |
| 320 | (concat "[ \t]*\\(\\(" |
| 321 | comment-regexp |
| 322 | "\\|dnl\\|[-|#;>*]+\\|(?[0-9]+[.)]\\)[ \t]*\\)*")) |
| 323 | (set (make-local-variable 'adaptive-fill-first-line-regexp) |
| 324 | "\\`\\([ \t]*dnl\\)?[ \t]*\\'") |
| 325 | |
| 326 | (when (fboundp 'filladapt-mode) |
| 327 | (unless gmpasm-filladapt-token-table |
| 328 | (setq gmpasm-filladapt-token-table |
| 329 | filladapt-token-table) |
| 330 | (setq gmpasm-filladapt-token-match-table |
| 331 | filladapt-token-match-table) |
| 332 | (setq gmpasm-filladapt-token-conversion-table |
| 333 | filladapt-token-conversion-table) |
| 334 | |
| 335 | ;; Numbered bullet points like "2.1" get matched at the start of a |
| 336 | ;; line when it's really something like "2.1 cycles/limb", so remove |
| 337 | ;; this from the list. The regexp for "1.", "2." etc is left |
| 338 | ;; though. |
| 339 | (gmpasm-remove-from-list 'gmpasm-filladapt-token-table |
| 340 | '("[0-9]+\\(\\.[0-9]+\\)+[ \t]" |
| 341 | bullet)) |
| 342 | |
| 343 | ;; "%" as a comment prefix interferes with register names on some |
| 344 | ;; CPUs, like %eax on x86, so remove this. |
| 345 | (gmpasm-remove-from-list 'gmpasm-filladapt-token-table |
| 346 | '("%+" postscript-comment)) |
| 347 | |
| 348 | (add-to-list 'gmpasm-filladapt-token-match-table |
| 349 | '(gmpasm-comment gmpasm-comment)) |
| 350 | (add-to-list 'gmpasm-filladapt-token-conversion-table |
| 351 | '(gmpasm-comment . exact))) |
| 352 | |
| 353 | (set (make-local-variable 'filladapt-token-table) |
| 354 | gmpasm-filladapt-token-table) |
| 355 | (set (make-local-variable 'filladapt-token-match-table) |
| 356 | gmpasm-filladapt-token-match-table) |
| 357 | (set (make-local-variable 'filladapt-token-conversion-table) |
| 358 | gmpasm-filladapt-token-conversion-table) |
| 359 | |
| 360 | ;; Add dnl and comment-start as fill prefixes. |
| 361 | ;; Comments in filladapt.el say filladapt-token-table must begin |
| 362 | ;; with ("^" beginning-of-line), so put our addition second. |
| 363 | (gmpasm-add-to-list-second 'filladapt-token-table |
| 364 | (list (concat "dnl[ \t]\\|" comment-regexp) |
| 365 | 'gmpasm-comment)))) |
| 366 | |
| 367 | (run-hooks 'gmpasm-mode-hook)) |
| 368 | |
| 369 | |
| 370 | (defun gmpasm-comment-region-dnl (beg end &optional arg) |
| 371 | "(gmpasm-comment-region-dnl BEG END &optional ARG) |
| 372 | |
| 373 | Comment or uncomment each line in the region using `dnl'. |
| 374 | With \\[universal-argument] prefix arg, uncomment each line in region. |
| 375 | This is `comment-region', but using \"dnl\"." |
| 376 | |
| 377 | (interactive "r\nP") |
| 378 | (let ((comment-start "dnl") |
| 379 | (comment-end "")) |
| 380 | (comment-region beg end arg))) |
| 381 | |
| 382 | |
| 383 | (provide 'gmpasm-mode) |
| 384 | |
| 385 | ;;; gmpasm-mode.el ends here |