]> git.eshelyaron.com Git - emacs.git/commitdiff
Improve autoconf-mode macro detection
authorBasil L. Contovounesios <basil@contovou.net>
Wed, 29 Jan 2025 13:05:39 +0000 (14:05 +0100)
committerEshel Yaron <me@eshelyaron.com>
Tue, 18 Feb 2025 08:58:44 +0000 (09:58 +0100)
* doc/lispref/modes.texi (Search-based Fontification): Fix
indentation of (MATCHER . FACESPEC) example.
* doc/misc/cc-mode.texi (Performance Issues): Index
defun-prompt-regexp under variables, not functions.

* lisp/progmodes/autoconf.el (autoconf--symbol, autoconf--macro):
New rx definitions.
(autoconf-definition-regexp): Use an optional second capture group
to indicate a function rather than variable definition.  Detect
AC_DEFINE defining a function-like CPP macro.  Skip more shell
syntax such as variable ${} expansion and command `` substitution in
AC_DEFINE_UNQUOTED variable.  Match AH_VERBATIM, AM_CONDITIONAL, and
AM_MISSING_PROG as defining variables, and AC_DEFUN, AC_DEFUN_ONCE,
AU_ALIAS, and AU_DEFUN as defining functions.  Document first
capture group in docstring.
(autoconf-font-lock-keywords): Use autoconf--macro to match more
Autoconf macros, such as those defined in the Autoconf Archive and
Gnulib.  Reserve font-lock-function-name-face for function
definitions as determined by autoconf-definition-regexp, and use
font-lock-variable-name-face for the rest instead.  Use Font Lock
face symbols directly in place of their corresponding variable.
Fontify M4 changequote primitive only as a standalone symbol.
(autoconf-imenu-generic-expression): Add commentary mentioning new
submenu possibility.
(autoconf-current-defun-function): Update docstring accuracy.
Replace line-end-position with pos-eol since there are no fields.
(autoconf-mode): Define defun-prompt-regexp in terms of
autoconf--macro to support more toplevel macros, such as those
defined in Autoheader, M4sh, etc.  Set
open-paren-in-column-0-is-defun-start to nil to avoid false
positives when an Autoconf quote character is in column zero.

* test/lisp/progmodes/autoconf-resources/configure.ac: New file.
* test/lisp/progmodes/autoconf-tests.el
(autoconf-tests-current-defun-function-define)
(autoconf-tests-current-defun-function-subst): Replace character
motion with search.
(autoconf-tests-autoconf-mode-comment-syntax): Ditto.  Test both dnl
and # comments.  Use syntax-ppss-context.
(autoconf-tests-font-lock): New test.

(cherry picked from commit 2e3cf73e055be69d42a6f84436d400caf0997abb)

doc/lispref/modes.texi
doc/misc/cc-mode.texi
lisp/progmodes/autoconf.el
test/lisp/progmodes/autoconf-resources/configure.ac [new file with mode: 0644]
test/lisp/progmodes/autoconf-tests.el

index 1ada6a4cf64429fd43a1083c751570f8705ebbd9..e9833c301af73c75b922d515325d91a30905e3e7 100644 (file)
@@ -3384,7 +3384,7 @@ However, @var{facespec} can also evaluate to a list of this form:
 
 @example
 (@var{subexp}
-(face @var{face} @var{prop1} @var{val1} @var{prop2} @var{val2}@dots{}))
+ (face @var{face} @var{prop1} @var{val1} @var{prop2} @var{val2}@dots{}))
 @end example
 
 @noindent
index 3f460f3bdb9f2aadc24296b19bf6fe837798cc4d..665cf8e41a8931ee29df03f4fbf7470c1c19a523 100644 (file)
@@ -7531,7 +7531,7 @@ caches syntactic information much better, so that the delay caused by
 searching for such a brace when it's not in column 0 is minimal,
 except perhaps when you've just moved a long way inside the file.
 
-@findex defun-prompt-regexp
+@vindex defun-prompt-regexp
 @vindex c-Java-defun-prompt-regexp
 @vindex Java-defun-prompt-regexp @r{(c-)}
 A special note about @code{defun-prompt-regexp} in Java mode: The common
index 8cee38cf0331ddf5701192830f2aacd3519b6508..c5b076bfacca4b2269ba3ad146751880921a90cb 100644 (file)
 (defvar autoconf-mode-hook nil
   "Hook run by `autoconf-mode'.")
 
+(rx-define autoconf--symbol (+ (| (syntax word) (syntax symbol))))
+
+;; Any Autoconf macro name.
+(rx-define autoconf--macro
+  (: (| "AC"  ;; Autoconf.
+        "AH"  ;; Autoheader.
+        "AM"  ;; Automake.
+        "AS"  ;; M4sh.
+        "AU"  ;; Autoupdate.
+        "AX"  ;; Autoconf Archive.
+        "LT"  ;; Libtool.
+        "gl") ;; Gnulib.
+     ?_ autoconf--symbol))
+
 (defconst autoconf-definition-regexp
-  "A\\(?:H_TEMPLATE\\|C_\\(?:SUBST\\|DEFINE\\(?:_UNQUOTED\\)?\\)\\)(\\[*\\(\\(?:\\sw\\|\\s_\\)+\\)\\]*")
+  ;; Historically this `defconst' defined only group #1.
+  ;; For internal Font Lock use, the presence of an optional group #2
+  ;; identifies a function rather than variable definition.
+  (rx-let ((argbeg (: ?\( (* ?\[)))
+           (argend (in "]),"))
+           (plaindef (: argbeg (group-n 1 autoconf--symbol))))
+    (rx symbol-start
+        (| (: "AC_DEFINE"
+              ;; AC_DEFINE and AC_DEFINE_UNQUOTED can define object- and
+              ;; function-like CPP macros.  An open-paren is easy to
+              ;; detect in the case of AC_DEFINE.  Doing the same for
+              ;; AC_DEFINE_UNQUOTED in the general case requires
+              ;; knowledge of shell syntax, so don't bother for now.
+              (| (: plaindef (? (group-n 2 ?\()))
+                 (: "_UNQUOTED" argbeg (group-n 1 (+ (not argend))))))
+           (: (| "AC_SUBST"
+                 "AH_TEMPLATE"
+                 "AH_VERBATIM"
+                 "AM_CONDITIONAL"
+                 "AM_MISSING_PROG"
+                 (group-n 2 (| "AC_DEFUN"
+                               "AC_DEFUN_ONCE"
+                               "AU_ALIAS"
+                               "AU_DEFUN")))
+              plaindef))))
+  "Matches Autoconf macro calls that define something.
+The thing being defined is captured in the first subexpression group.")
 
 (defvar autoconf-font-lock-keywords
-  `(("\\_<\\(?:A[CHMS]\\|LT\\)_\\(?:\\sw\\|\\s_\\)+" . font-lock-keyword-face)
+  `(,(rx symbol-start autoconf--macro)
     (,autoconf-definition-regexp
-     1 font-lock-function-name-face)
+     1 (if (match-beginning 2)
+           'font-lock-function-name-face
+         'font-lock-variable-name-face))
     ;; Are any other M4 keywords really appropriate for configure.ac,
     ;; given that we do `dnl'?
-    ("changequote" . font-lock-keyword-face)))
+    "\\_<changequote\\_>"))
 
 (defvar autoconf-mode-syntax-table
   (let ((table (make-syntax-table)))
     table))
 
 (defvar autoconf-imenu-generic-expression
+  ;; This lists both variable-like and function-like definitions in a
+  ;; flat list, but they could be distinguished if desired.
   (list (list nil autoconf-definition-regexp 1)))
 
 ;; It's not clear how best to implement this.
 (defun autoconf-current-defun-function ()
   "Function to use for `add-log-current-defun-function' in Autoconf mode.
-This version looks back for an AC_DEFINE or AC_SUBST.  It will stop
-searching backwards at another AC_... command."
+This version looks back for a definition such as by AC_DEFINE or
+AC_SUBST.  It stops searching when it encounters other Autoconf macros."
   (save-excursion
-    (skip-syntax-forward "w_" (line-end-position))
+    (skip-syntax-forward "w_" (pos-eol))
     (if (re-search-backward autoconf-definition-regexp
                             (save-excursion (beginning-of-defun) (point))
                             t)
@@ -77,14 +121,15 @@ searching backwards at another AC_... command."
 (define-derived-mode autoconf-mode prog-mode "Autoconf"
   "Major mode for editing Autoconf configure.ac files."
   (setq-local parens-require-spaces nil) ; for M4 arg lists
-  (setq-local defun-prompt-regexp "^[ \t]*A[CM]_\\(\\sw\\|\\s_\\)+")
+  ;; FIXME: Should indented macro calls really count as defuns?
+  (setq-local defun-prompt-regexp (rx bol (* (in "\t ")) autoconf--macro))
+  (setq-local open-paren-in-column-0-is-defun-start nil)
   (setq-local comment-start "dnl ")
   ;; We want to avoid matching "dnl" in other text.
   (setq-local comment-start-skip "\\(?:\\(\\W\\|^\\)dnl\\|#\\) +")
   (setq-local syntax-propertize-function
              (syntax-propertize-rules ("\\<dnl\\>" (0 "<"))))
-  (setq-local font-lock-defaults
-             '(autoconf-font-lock-keywords nil nil))
+  (setq-local font-lock-defaults '(autoconf-font-lock-keywords))
   (setq-local imenu-generic-expression autoconf-imenu-generic-expression)
   (setq-local indent-line-function #'indent-relative)
   (setq-local add-log-current-defun-function
diff --git a/test/lisp/progmodes/autoconf-resources/configure.ac b/test/lisp/progmodes/autoconf-resources/configure.ac
new file mode 100644 (file)
index 0000000..b05c556
--- /dev/null
@@ -0,0 +1,172 @@
+# Indentation.
+
+AC_PROG_CC
+dnl <- font-lock-keyword-face
+ AC_PROG_CC
+ dnl <- font-lock-keyword-face
+  AC_PROG_CC
+  dnl <- font-lock-keyword-face
+
+# Quoting.
+
+   [AC_PROG_CC] [[AC_PREREQ([2.70])]]
+dnl ^^^^^^^^^^    ^^^^^^^^^ font-lock-keyword-face
+
+# Nesting.
+
+AS_VAR_IF([foo], [bar], [AC_MSG_FAILURE([baz])])
+dnl <- font-lock-keyword-face
+dnl                      ^^^^^^^^^^^^^^ font-lock-keyword-face
+AS_VAR_IF([foo], [bar],
+dnl <- font-lock-keyword-face
+   [AC_MSG_FAILURE([baz])])
+dnl ^^^^^^^^^^^^^^ font-lock-keyword-face
+
+# Autoconf.
+
+AC_PROG_CC AC_PREREQ(2.70) AC_PREREQ([2.70])
+dnl <- font-lock-keyword-face
+dnl        ^^^^^^^^^       ^^^^^^^^^ font-lock-keyword-face
+
+# Autoheader.
+
+AH_HEADER AH_TOP(foo) AH_TOP([foo])
+dnl <- font-lock-keyword-face
+dnl       ^^^^^^      ^^^^^^ font-lock-keyword-face
+
+# Automake.
+
+AM_PATH_LISPDIR AM_SILENT_RULES(yes) AM_SILENT_RULES([yes])
+dnl <- font-lock-keyword-face
+dnl             ^^^^^^^^^^^^^^^      ^^^^^^^^^^^^^^^ font-lock-keyword-face
+
+# M4sh.
+
+AS_INIT AS_ECHO(foo) AS_ECHO([foo])
+dnl <- font-lock-keyword-face
+dnl     ^^^^^^^      ^^^^^^^ font-lock-keyword-face
+
+# Autoconf Archive.
+
+AX_ADD_FORTIFY_SOURCE AX_SAVE_FLAGS(foo) AX_SAVE_FLAGS([foo])
+dnl <- font-lock-keyword-face
+dnl                   ^^^^^^^^^^^^^      ^^^^^^^^^^^^^ font-lock-keyword-face
+
+# Libtool.
+
+LT_OUTPUT LT_PREREQ(2.4.6) LT_PREREQ([2.4.6])
+dnl <- font-lock-keyword-face
+dnl       ^^^^^^^^^        ^^^^^^^^^ font-lock-keyword-face
+
+# Gnulib.
+
+gl_EARLY gl_WARN_ADD(foo) gl_WARN_ADD([foo])
+dnl <- font-lock-keyword-face
+dnl      ^^^^^^^^^^^      ^^^^^^^^^^^ font-lock-keyword-face
+
+# M4.
+
+changequote(<<, >>) m4_changequote(<<, >>)
+dnl <- font-lock-keyword-face
+dnl                 ^^^^^^^^^^^^^^ !font-lock-keyword-face
+changequote([, ]) m4_changequote([, ])
+dnl <- font-lock-keyword-face
+dnl               ^^^^^^^^^^^^^^ !font-lock-keyword-face
+
+# AC_DEFINE object-like macro.
+
+AC_DEFINE(a) AC_DEFINE(a, d)
+dnl <- font-lock-keyword-face
+dnl          ^^^^^^^^^ font-lock-keyword-face
+dnl       ^            ^ font-lock-variable-name-face
+AC_DEFINE([a]) AC_DEFINE([a], d)
+dnl        ^              ^ font-lock-variable-name-face
+AC_DEFINE([[a]]) AC_DEFINE([[a]], d)
+dnl         ^                ^ font-lock-variable-name-face
+AC_DEFINE(bc) AC_DEFINE(bc, d)
+dnl       ^^            ^^ font-lock-variable-name-face
+AC_DEFINE([bc]) AC_DEFINE([bc], d)
+dnl        ^^              ^^ font-lock-variable-name-face
+AC_DEFINE([[bc]]) AC_DEFINE([[bc]], d)
+dnl         ^^                ^^ font-lock-variable-name-face
+
+# AC_DEFINE function-like macro.
+
+AC_DEFINE(a()) AC_DEFINE(a(), d)
+dnl <- font-lock-keyword-face
+dnl            ^^^^^^^^^ font-lock-keyword-face
+dnl       ^              ^ font-lock-function-name-face
+AC_DEFINE([a()]) AC_DEFINE([a()], d)
+dnl        ^                ^ font-lock-function-name-face
+AC_DEFINE([[a()]]) AC_DEFINE([[a()]], d)
+dnl         ^                  ^ font-lock-function-name-face
+AC_DEFINE(a(x)) AC_DEFINE(a(x), d)
+dnl       ^               ^ font-lock-function-name-face
+AC_DEFINE([a(x)]) AC_DEFINE([a(x)], d)
+dnl        ^                 ^ font-lock-function-name-face
+AC_DEFINE([[a(x)]]) AC_DEFINE([[a(x)]], d)
+dnl         ^                   ^ font-lock-function-name-face
+AC_DEFINE(a(x, y)) AC_DEFINE(a(x, y), d)
+dnl       ^                  ^ font-lock-function-name-face
+AC_DEFINE([a(x, y)]) AC_DEFINE([a(x, y)], d)
+dnl        ^                    ^ font-lock-function-name-face
+AC_DEFINE([[a(x, y)]]) AC_DEFINE([[a(x, y)]], d)
+dnl         ^                      ^ font-lock-function-name-face
+
+# AC_DEFINE_UNQUOTED object-like macro.
+
+AC_DEFINE_UNQUOTED(a) AC_DEFINE_UNQUOTED(a, d)
+dnl <- font-lock-keyword-face
+dnl                   ^^^^^^^^^^^^^^^^^^ font-lock-keyword-face
+dnl                ^                     ^ font-lock-variable-name-face
+AC_DEFINE_UNQUOTED([a]) AC_DEFINE_UNQUOTED([a], d)
+dnl                 ^                       ^ font-lock-variable-name-face
+AC_DEFINE_UNQUOTED([[a]]) AC_DEFINE_UNQUOTED([[a]], d)
+dnl                  ^                         ^ font-lock-variable-name-face
+AC_DEFINE_UNQUOTED(bc) AC_DEFINE_UNQUOTED(bc, d)
+dnl                ^^                     ^^ font-lock-variable-name-face
+AC_DEFINE_UNQUOTED([bc]) AC_DEFINE_UNQUOTED([bc], d)
+dnl                 ^^                       ^^ font-lock-variable-name-face
+AC_DEFINE_UNQUOTED([[bc]]) AC_DEFINE_UNQUOTED([[bc]], d)
+dnl                  ^^                         ^^ font-lock-variable-name-face
+AC_DEFINE_UNQUOTED(\a`b`$c${d})
+dnl                ^^^^^^^^^^^ font-lock-variable-name-face
+AC_DEFINE_UNQUOTED([\a`b`$c${d}])
+dnl                 ^^^^^^^^^^^ font-lock-variable-name-face
+AC_DEFINE_UNQUOTED([[\a`b`$c${d}]])
+dnl                  ^^^^^^^^^^^ font-lock-variable-name-face
+AC_DEFINE_UNQUOTED(\a`b`$c${d}, e)
+dnl                ^^^^^^^^^^^ font-lock-variable-name-face
+AC_DEFINE_UNQUOTED([\a`b`$c${d}], e)
+dnl                 ^^^^^^^^^^^ font-lock-variable-name-face
+AC_DEFINE_UNQUOTED([[\a`b`$c${d}]], e)
+dnl                  ^^^^^^^^^^^ font-lock-variable-name-face
+
+# Variable definition.
+
+AC_SUBST(a) AC_SUBST(a, b)
+dnl <- font-lock-keyword-face
+dnl         ^^^^^^^^ font-lock-keyword-face
+dnl      ^           ^ font-lock-variable-name-face
+AH_TEMPLATE(a, b) AH_VERBATIM(a, b)
+dnl <- font-lock-keyword-face
+dnl               ^^^^^^^^^^^ font-lock-keyword-face
+dnl         ^                 ^font-lock-variable-name-face
+AM_CONDITIONAL(a, b) AM_MISSING_PROG(a, b)
+dnl <- font-lock-keyword-face
+dnl                  ^^^^^^^^^^^^^^^ font-lock-keyword-face
+dnl            ^                     ^ font-lock-variable-name-face
+
+# Function definition.
+
+AC_DEFUN(a) AC_DEFUN(a, b)
+dnl <- font-lock-keyword-face
+dnl         ^^^^^^^^ font-lock-keyword-face
+dnl      ^           ^ font-lock-function-name-face
+AC_DEFUN_ONCE(a, b)
+dnl <- font-lock-keyword-face
+dnl           ^ font-lock-function-name-face
+AU_ALIAS(a, b) AU_DEFUN(a, b)
+dnl <- font-lock-keyword-face
+dnl            ^^^^^^^^ font-lock-keyword-face
+dnl      ^              ^ font-lock-function-name-face
index d4c99218fa06a1e426f9e39c18b99836c850df4c..df20986fc52737ddbef725b8a0bea9a4c83b67f6 100644 (file)
 ;; You should have received a copy of the GNU General Public License
 ;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
 
-;;; Commentary:
-
-;;
-
 ;;; Code:
 
 (require 'autoconf)
 (require 'ert)
+(require 'ert-font-lock)
 
 (ert-deftest autoconf-tests-current-defun-function-define ()
   (with-temp-buffer
+    (autoconf-mode)
     (insert "AC_DEFINE([HAVE_RSVG], [1], [Define to 1 if using librsvg.])")
+    (let ((def "HAVE_RSVG"))
+      (search-backward def)
+      (should (equal (autoconf-current-defun-function) def)))
     (goto-char (point-min))
-    (should-not (autoconf-current-defun-function))
-    (forward-char 11)
-    (should (equal (autoconf-current-defun-function) "HAVE_RSVG"))))
+    (should-not (autoconf-current-defun-function))))
 
 (ert-deftest autoconf-tests-current-defun-function-subst ()
   (with-temp-buffer
+    (autoconf-mode)
     (insert "AC_SUBST([srcdir])")
+    (let ((def "srcdir"))
+      (search-backward def)
+      (should (equal (autoconf-current-defun-function) "srcdir")))
     (goto-char (point-min))
-    (should-not (autoconf-current-defun-function))
-    (forward-char 10)
-    (should (equal (autoconf-current-defun-function) "srcdir"))))
+    (should-not (autoconf-current-defun-function))))
 
 (ert-deftest autoconf-tests-autoconf-mode-comment-syntax ()
   (with-temp-buffer
     (autoconf-mode)
-    (insert "dnl  Autoconf script for GNU Emacs")
-    (should (nth 4 (syntax-ppss)))))
+    (dolist (start '("dnl" "#"))
+      (insert start "  Autoconf script for GNU Emacs")
+      (should (eq (syntax-ppss-context (syntax-ppss)) 'comment))
+      (insert "\n")
+      (should-not (syntax-ppss-context (syntax-ppss))))))
+
+(ert-font-lock-deftest-file autoconf-tests-font-lock
+  "Test `autoconf-mode' font lock."
+  autoconf-mode "configure.ac")
 
 (provide 'autoconf-tests)
 ;;; autoconf-tests.el ends here