diff --git a/lisp/progmodes/m4-mode.el b/lisp/progmodes/m4-mode.el index eff74530c3b..9f3d2bb3448 100644 --- a/lisp/progmodes/m4-mode.el +++ b/lisp/progmodes/m4-mode.el @@ -50,7 +50,6 @@ If m4 is not in your PATH, set this to an absolute file name." :version "24.4" :type 'file) -;;options to m4 (defcustom m4-program-options nil "Options to pass to `m4-program'." :type '(repeat string)) @@ -60,34 +59,40 @@ If m4 is not in your PATH, set this to an absolute file name." ;;or ;;(defconst m4-program-options '("--prefix-builtins")) -;; Needed at compile-time for `m4-font-lock-keywords' below. -(eval-and-compile - (defconst m4--macro-list - ;; From (info "(m4) Macro index") - '("__file__" "__gnu__" "__line__" "__os2__" "__program__" "__unix__" - "__windows__" "argn" "array" "array_set" "builtin" "capitalize" - "changecom" "changequote" "changeword" "cleardivert" "cond" "copy" - "curry" "debugfile" "debugmode" "decr" "define" "define_blind" - "defn" "divert" "divnum" "dnl" "downcase" "dquote" "dquote_elt" - "dumpdef" "errprint" "esyscmd" "eval" "example" "exch" - "fatal_error" "file" "foreach" "foreachq" "forloop" "format" "gnu" - "ifdef" "ifelse" "include" "incr" "index" "indir" "join" "joinall" - "len" "line" "m4exit" "m4wrap" "maketemp" "mkstemp" "nargs" "os2" - "patsubst" "popdef" "pushdef" "quote" "regexp" "rename" "reverse" - "shift" "sinclude" "stack_foreach" "stack_foreach_lifo" - "stack_foreach_sep" "stack_foreach_sep_lifo" "substr" "syscmd" - "sysval" "traceoff" "traceon" "translit" "undefine" "undivert" - "unix" "upcase" "windows") - "List of valid m4 macros for M4 mode.")) +;; Matches the name of m4 built-in macros. +;; From (info "(m4) Macro index"), modulo sample composite macros. +(rx-define m4--builtin + (| "__file__" "__gnu__" "__line__" "__os2__" "__program__" "__unix__" + "__windows__" "builtin" "changecom" "changequote" "changeword" + "debugfile" "debugmode" "decr" "define" "defn" "divert" "divnum" + "dnl" "dumpdef" "errprint" "esyscmd" "eval" "format" "ifdef" "ifelse" + "include" "incr" "index" "indir" "len" "m4exit" "m4wrap" "maketemp" + "mkstemp" "os2" "patsubst" "popdef" "pushdef" "regexp" "shift" + "sinclude" "substr" "syscmd" "sysval" "traceoff" "traceon" "translit" + "undefine" "undivert" "unix" "windows")) + +;; Matches the name of m4 composite macros with analogues in M4sugar. +(rx-define m4--autoconf + (| "argn" "cleardivert" "cond" "copy" "curry" "dquote" "dquote_elt" + "foreach" "join" "joinall" "quote" "rename" "reverse" "stack_foreach" + "stack_foreach_lifo" "stack_foreach_sep" "stack_foreach_sep_lifo")) + +;; Matches the name of other m4 composite macros given as examples in +;; the manual or sources of GNU M4. +(rx-define m4--composite + (| "array" "array_set" "capitalize" "define_blind" "downcase" + "exch" "fatal_error" "foreachq" "forloop" "nargs" "upcase")) (defvar m4-font-lock-keywords (eval-when-compile - `(("\\(\\_<\\(m4_\\)?dnl\\_>\\).*$" (0 font-lock-comment-face t)) - ("\\$[*#@0-9]" . font-lock-variable-name-face) - ("\\$@" . font-lock-variable-name-face) - ("\\$\\*" . font-lock-variable-name-face) - (,(concat "\\_<\\(m4_\\)?" (regexp-opt m4--macro-list) "\\_>") - . font-lock-keyword-face))) + `(("\\(\\_<\\(?:m4_\\)?dnl\\_>\\)\\(.*\\)$" + (1 'font-lock-comment-delimiter-face t) + (2 'font-lock-comment-face t)) + ("\\$[*#@0-9]" . 'font-lock-variable-use-face) + ,(rx symbol-start + (? "m4_") + (| m4--builtin m4--autoconf m4--composite) + symbol-end))) "Default `font-lock-keywords' for M4 mode.") (defcustom m4-mode-hook nil @@ -98,11 +103,11 @@ If m4 is not in your PATH, set this to an absolute file name." (defvar m4-mode-syntax-table (let ((table (make-syntax-table))) (modify-syntax-entry ?` "('" table) - (modify-syntax-entry ?' ")`" table) + (modify-syntax-entry ?\' ")`" table) (modify-syntax-entry ?# "<\n" table) (modify-syntax-entry ?\n ">#" table) - (modify-syntax-entry ?{ "." table) - (modify-syntax-entry ?} "." table) + (modify-syntax-entry ?\{ "." table) + (modify-syntax-entry ?\} "." table) (modify-syntax-entry ?_ "_" table) (modify-syntax-entry ?* "." table) (modify-syntax-entry ?\" "." table) @@ -140,27 +145,26 @@ If m4 is not in your PATH, set this to an absolute file name." (defun m4-m4-buffer () "Send contents of the current buffer to m4." (interactive) - (shell-command-on-region - (point-min) (point-max) - (mapconcat #'identity (cons m4-program m4-program-options) "\s") - "*m4-output*" nil) - (switch-to-buffer-other-window "*m4-output*")) + (m4-m4-region (point-min) (point-max))) -(defun m4-m4-region () - "Send contents of the current region to m4." - (interactive) - (shell-command-on-region - (point) (mark) - (mapconcat #'identity (cons m4-program m4-program-options) "\s") - "*m4-output*" nil) - (switch-to-buffer-other-window "*m4-output*")) +(defun m4-m4-region (&optional start end) + "Send the contents of the region between START and END to m4. +When nil, START and END default to the active region." + (interactive "r") + (let ((buf "*m4-output*")) + (shell-command-on-region + (or start (region-beginning)) (or end (region-end)) + (mapconcat #'shell-quote-argument (cons m4-program m4-program-options) " ") + buf nil nil nil (use-region-noncontiguous-p)) + (pop-to-buffer buf))) (defun m4-current-defun-name () "Return the name of the M4 function at point, or nil." (save-excursion (if (re-search-backward - "^\\(\\(m4_\\)?define\\|A._DEFUN\\)(\\[?\\([A-Za-z0-9_]+\\)" nil t) - (match-string-no-properties 3)))) + "^\\(?:\\(?:m4_\\)?define\\|A._DEFUN\\)([[`]?\\([A-Za-z0-9_]+\\)" + nil t) + (match-string-no-properties 1)))) ;;;###autoload (define-derived-mode m4-mode prog-mode "m4" @@ -169,7 +173,7 @@ If m4 is not in your PATH, set this to an absolute file name." (setq-local parse-sexp-ignore-comments t) (setq-local add-log-current-defun-function #'m4-current-defun-name) (setq-local syntax-propertize-function m4-syntax-propertize) - (setq-local font-lock-defaults '(m4-font-lock-keywords nil))) + (setq-local font-lock-defaults '(m4-font-lock-keywords))) (provide 'm4-mode) ;;stuff to play with for debugging diff --git a/test/lisp/progmodes/m4-mode-resources/font-lock.m4 b/test/lisp/progmodes/m4-mode-resources/font-lock.m4 new file mode 100644 index 00000000000..90a6f333cf9 --- /dev/null +++ b/test/lisp/progmodes/m4-mode-resources/font-lock.m4 @@ -0,0 +1,30 @@ +# comment +# <- font-lock-comment-delimiter-face +# ^ font-lock-comment-face + + #comment +# ^ font-lock-comment-delimiter-face +# ^ font-lock-comment-face + +dnl discarded +# <- font-lock-comment-delimiter-face +# ^ font-lock-comment-face + + dnl`'discarded +# ^^^ font-lock-comment-delimiter-face +# ^^^ font-lock-comment-face + +define(`a', `$#`$*'$@') +# <- font-lock-keyword-face +# ^^ ^^ ^^ font-lock-variable-use-face + + m4_define(`b', ``$0$1$2$3$4`'$5$6$7$8$9'') +# ^^^^^^^^^ font-lock-keyword-face +# ^^^^^^^^^^ ^^^^^^^^^^ font-lock-variable-use-face + + m4`'_define(`c', `') +# ^^^^^^^^^^^ !font-lock-keyword-face + +m4_`'define(`d', `') +# <- !font-lock-keyword-face +# ^^^^^^ font-lock-keyword-face diff --git a/test/lisp/progmodes/m4-mode-tests.el b/test/lisp/progmodes/m4-mode-tests.el new file mode 100644 index 00000000000..e04d7d49fa8 --- /dev/null +++ b/test/lisp/progmodes/m4-mode-tests.el @@ -0,0 +1,55 @@ +;;; m4-mode-tests.el --- tests for m4-mode.el -*- lexical-binding: t -*- + +;; Copyright (C) 2025 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . + +;;; Code: + +(require 'ert) +(require 'ert-font-lock) +(require 'm4-mode) + +(ert-deftest m4-current-defun-name () + "Test `m4-current-defun-name' behavior." + (with-temp-buffer + (m4-mode) + (insert "define(`a', `')") + (should (equal (m4-current-defun-name) "a")) + ;; Not at toplevel. + (insert "define(`b', `')") + (should (equal (m4-current-defun-name) "a")) + (insert "\nm4_define(`c', `')") + (should (equal (m4-current-defun-name) "c")) + (insert "\nAC_DEFUN([d], [])") + (should (equal (m4-current-defun-name) "d")) + (insert "\nAU_DEFUN([e], [])") + (should (equal (m4-current-defun-name) "e")))) + +(ert-deftest m4-mode-comment-syntax () + "Test `m4-mode' comment syntax." + (with-temp-buffer + (m4-mode) + (insert "# comment") + (should (eq (syntax-ppss-context (syntax-ppss)) 'comment)) + (insert ?\n) + (should-not (syntax-ppss-context (syntax-ppss))))) + +(ert-font-lock-deftest-file m4-mode-font-lock + "Test `m4-mode' font lock." + m4-mode "font-lock.m4") + +;;; m4-mode-tests.el ends here