emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

POC: customizable cc-mode keywords


From: Daniel Colascione
Subject: POC: customizable cc-mode keywords
Date: Thu, 01 May 2014 22:26:07 -0700
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Thunderbird/24.5.0

cc-mode has trouble with parsing dialects of C that use the preprocessor
heavily. Consider this example from the Linux kernel:

  static int perf_event_period(struct perf_event *event, u64 __user *arg)

__user is defined to some GCC static analysis nonsense, but since
cc-mode doesn't know that, we see __user fontified in
font-lock-variable-name-face and *arg untouched. This example is fairly
benign (if ugly), but there are other cases where variations in
pre-processor C dialect confuse cc-mode in larger regions, leading to
odd fontification and indentation.

The patch below adds customizable options for additional C-family
language "keywords".

To add this feature, we have to change how cc-mode evaluates its
language variables. Today, we use clever macros to hard-code the values
of all cc-mode language variables into the mode functions of each
cc-mode major mode function or into c-init-language-vars-for, but in
order to allow users to customize cc-mode syntax, we have to be able to
recompute language constants and variables at runtime. The new code
simply evaluates cc-mode language setter forms at mode initialization
instead. This approach is slower, but not by much: it takes 0.9ms to set
up cc-mode's ~130 language variables using the precompiled function
approach, while it takes 1.6ms to do the same work using dynamic
evaluation. I can live with this performance regression.

As implemented, the keyword list can only be customized globally, but
it'd be nice to be able to do something buffer-local too.

=== modified file 'lisp/progmodes/cc-defs.el'
--- lisp/progmodes/cc-defs.el   2014-02-09 12:34:25 +0000
+++ lisp/progmodes/cc-defs.el   2014-05-02 04:47:35 +0000
@@ -89,7 +89,7 @@
 
 ;;; Variables also used at compile time.

-(defconst c-version "5.32.5"
+(defconst c-version "5.32.5.1"
   "CC Mode version number.")

 (defconst c-version-sym (intern c-version))
@@ -1812,8 +1812,6 @@
 ;; and other miscellaneous data.  The obarray might also contain
 ;; various other symbols, but those don't have any variable bindings.

-(defvar c-lang-const-expansion nil)
-
 (defsubst c-get-current-file ()
   ;; Return the base name of the current file.
   (let ((file (cond
@@ -1880,19 +1878,6 @@
 constant.  A file is identified by its base name."

   (let* ((sym (intern (symbol-name name) c-lang-constants))
-        ;; Make `c-lang-const' expand to a straightforward call to
-        ;; `c-get-lang-constant' in `cl-macroexpand-all' below.
-        ;;
-        ;; (The default behavior, i.e. to expand to a call inside
-        ;; `eval-when-compile' should be equivalent, since that macro
-        ;; should only expand to its content if it's used inside a
-        ;; form that's already evaluated at compile time.  It's
-        ;; however necessary to use our cover macro
-        ;; `cc-eval-when-compile' due to bugs in `eval-when-compile',
-        ;; and it expands to a bulkier form that in this case only is
-        ;; unnecessary garbage that we don't want to store in the
-        ;; language constant source definitions.)
-        (c-lang-const-expansion 'call)
         (c-langs-are-parametric t)
         bindings
         pre-files)
@@ -2037,53 +2022,28 @@
         "Unknown language %S since it got no `c-mode-prefix' property"
         (symbol-name lang))))

-    (if (eq c-lang-const-expansion 'immediate)
-       ;; No need to find out the source file(s) when we evaluate
-       ;; immediately since all the info is already there in the
-       ;; `source' property.
-       `',(c-get-lang-constant name nil mode)
-
-      (let ((file (c-get-current-file)))
-       (if file (setq file (intern file)))
-       ;; Get the source file(s) that must be loaded to get the value
-       ;; of the constant.  If the symbol isn't defined yet we assume
-       ;; that its definition will come later in this file, and thus
-       ;; are no file dependencies needed.
-       (setq source-files (nreverse
-                           ;; Reverse to get the right load order.
-                           (apply 'nconc
-                                  (mapcar (lambda (elem)
-                                            (if (eq file (car elem))
-                                                nil ; Exclude our own file.
-                                              (list (car elem))))
-                                          (get sym 'source))))))
-
-      ;; Make some effort to do a compact call to
-      ;; `c-get-lang-constant' since it will be compiled in.
-      (setq args (and mode `(',mode)))
-      (if (or source-files args)
-         (setq args (cons (and source-files `',source-files)
-                          args)))
-
-      (if (or (eq c-lang-const-expansion 'call)
-             (and (not c-lang-const-expansion)
-                  (not mode))
-             load-in-progress
-             (not (boundp 'byte-compile-dest-file))
-             (not (stringp byte-compile-dest-file)))
-         ;; Either a straight call is requested in the context, or
-         ;; we're in an "uncontrolled" context and got no language,
-         ;; or we're not being byte compiled so the compile time
-         ;; stuff below is unnecessary.
-         `(c-get-lang-constant ',name ,@args)
-
-       ;; Being compiled.  If the loading and compiling version is
-       ;; the same we use a value that is evaluated at compile time,
-       ;; otherwise it's evaluated at runtime.
-       `(if (eq c-version-sym ',c-version-sym)
-            (cc-eval-when-compile
-              (c-get-lang-constant ',name ,@args))
-          (c-get-lang-constant ',name ,@args))))))
+    (let ((file (c-get-current-file)))
+      (if file (setq file (intern file)))
+      ;; Get the source file(s) that must be loaded to get the value
+      ;; of the constant.  If the symbol isn't defined yet we assume
+      ;; that its definition will come later in this file, and thus
+      ;; are no file dependencies needed.
+      (setq source-files (nreverse
+                          ;; Reverse to get the right load order.
+                          (apply 'nconc
+                                 (mapcar (lambda (elem)
+                                           (if (eq file (car elem))
+                                               nil ; Exclude our own file.
+                                             (list (car elem))))
+                                         (get sym 'source))))))
+
+    ;; Make some effort to do a compact call to `c-get-lang-constant'
+    ;; and omit unneeded arguments since this code will be compiled.
+    (setq args (and mode `(',mode)))
+    (if (or source-files args)
+        (setq args (cons (and source-files `',source-files)
+                         args)))
+    `(c-get-lang-constant ',name ,@args)))

 (defvar c-lang-constants-under-evaluation nil)

@@ -2262,6 +2222,18 @@
             (setq buf-mode (get buf-mode 'c-fallback-mode))))
     match))

+(defun c-clear-value-cache ()
+  "Forget already-computed `c-lang-defvar' values.
+Call this function to make changes to cc-mode language
+variables take effect at the next mode initialization."
+  ;; Clear cached constant values
+  (mapatoms (lambda (sym)
+              (set sym nil))
+            c-lang-constants)
+  ;; Recompute our font lock keyword constants
+  (when (featurep 'cc-fonts)
+    (load "cc-fonts" nil t)))
+
 
 (cc-provide 'cc-defs)


=== modified file 'lisp/progmodes/cc-langs.el'
--- lisp/progmodes/cc-langs.el  2014-01-01 07:43:34 +0000
+++ lisp/progmodes/cc-langs.el  2014-05-02 05:19:24 +0000
@@ -1921,15 +1921,13 @@
   ;; declaration.  Specifically, they aren't recognized in the middle
   ;; of multi-token types, inside declarators, and between the
   ;; identifier and the arglist paren of a function declaration.
-  ;;
-  ;; FIXME: This ought to be user customizable since compiler stuff
-  ;; like this usually is wrapped in project specific macros.  (It'd
-  ;; of course be even better if we could cope without knowing this.)
-  t nil
-  (c c++) '(;; GCC extension.
-           "__attribute__"
-           ;; MSVC extension.
-           "__declspec"))
+  t (when (boundp (c-mode-symbol "extra-keywords"))
+      (mapcar #'car (c-mode-var "extra-keywords")))
+  (c c++) (append (c-lang-const c-decl-hangon-kwds)
+                  '( ;; GCC extension.
+                    "__attribute__"
+                    ;; MSVC extension.
+                    "__declspec")))

 (c-lang-defconst c-decl-hangon-key
   ;; Adorned regexp matching `c-decl-hangon-kwds'.
@@ -2120,11 +2118,18 @@
 (c-lang-defconst c-paren-nontype-kwds
   "Keywords that may be followed by a parenthesis expression that doesn't
 contain type identifiers."
-  t       nil
-  (c c++) '(;; GCC extension.
-           "__attribute__"
-           ;; MSVC extension.
-           "__declspec"))
+  t       (when (boundp (c-mode-symbol "extra-keywords"))
+            (apply 'nconc
+                   (mapcar (lambda (kw)
+                             (when (cdr kw)
+                               (list (car kw))))
+                           (c-mode-var "extra-keywords"))))
+  (c c++) (append
+           (c-lang-const c-paren-nontype-kwds)
+           '( ;; GCC extension.
+             "__attribute__"
+             ;; MSVC extension.
+             "__declspec")))

 (c-lang-defconst c-paren-type-kwds
   "Keywords that may be followed by a parenthesis expression containing
@@ -3155,115 +3160,38 @@

 ;; Make the `c-lang-setvar' variables buffer local in the current buffer.
 ;; These are typically standard emacs variables such as `comment-start'.
-(defmacro c-make-emacs-variables-local ()
-  `(progn
-     ,@(mapcar (lambda (init)
-                `(make-local-variable ',(car init)))
-              (cdr c-emacs-variable-inits))))
-
-(defun c-make-init-lang-vars-fun (mode)
-  "Create a function that initializes all the language dependent variables
-for the given mode.
-
-This function should be evaluated at compile time, so that the
-function it returns is byte compiled with all the evaluated results
-from the language constants.  Use the `c-init-language-vars' macro to
-accomplish that conveniently."
-
-  (if (and (not load-in-progress)
-          (boundp 'byte-compile-dest-file)
-          (stringp byte-compile-dest-file))
-
-      ;; No need to byte compile this lambda since the byte compiler is
-      ;; smart enough to detect the `funcall' construct in the
-      ;; `c-init-language-vars' macro below and compile it all straight
-      ;; into the function that contains `c-init-language-vars'.
-      `(lambda ()
-
-        ;; This let sets up the context for `c-mode-var' and similar
-        ;; that could be in the result from `cl-macroexpand-all'.
-        (let ((c-buffer-is-cc-mode ',mode)
-              current-var source-eval)
-          (c-make-emacs-variables-local)
-          (condition-case err
-
-              (if (eq c-version-sym ',c-version-sym)
-                  (setq ,@(let ((c-buffer-is-cc-mode mode)
-                                (c-lang-const-expansion 'immediate))
-                            ;; `c-lang-const' will expand to the evaluated
-                            ;; constant immediately in `cl-macroexpand-all'
-                            ;; below.
-                             (mapcan
-                              (lambda (init)
-                                `(current-var ',(car init)
-                                  ,(car init) ,(cl-macroexpand-all
-                                                (elt init 1))))
-                              ;; Note: The following `append' copies the
-                              ;; first argument.  That list is small, so
-                              ;; this doesn't matter too much.
-                             (append (cdr c-emacs-variable-inits)
-                                     (cdr c-lang-variable-inits)))))
-
-                ;; This diagnostic message isn't useful for end
-                ;; users, so it's disabled.
-                ;;(unless (get ',mode 'c-has-warned-lang-consts)
-                ;;  (message ,(concat "%s compiled with CC Mode %s "
-                ;;                    "but loaded with %s - evaluating "
-                ;;                    "language constants from source")
-                ;;           ',mode ,c-version c-version)
-                ;;  (put ',mode 'c-has-warned-lang-consts t))
-
-                (setq source-eval t)
-                (let ((init ',(append (cdr c-emacs-variable-inits)
-                                      (cdr c-lang-variable-inits))))
-                  (while init
-                    (setq current-var (caar init))
-                    (set (caar init) (eval (cadar init)))
-                    (setq init (cdr init)))))
-
-            (error
-             (if current-var
-                 (message "Eval error in the `c-lang-defvar' or 
`c-lang-setvar' for
`%s'%s: %S"
-                          current-var
-                          (if source-eval
-                              (format "\
- (fallback source eval - %s compiled with CC Mode %s but loaded with %s)"
-                                      ',mode ,c-version c-version)
-                            "")
-                          err)
-               (signal (car err) (cdr err)))))))
-
-    ;; Being evaluated from source.  Always use the dynamic method to
-    ;; work well when `c-lang-defvar's in this file are reevaluated
-    ;; interactively.
-    `(lambda ()
-       (require 'cc-langs)
-       (let ((c-buffer-is-cc-mode ',mode)
-            (init (append (cdr c-emacs-variable-inits)
-                          (cdr c-lang-variable-inits)))
-            current-var)
-        (c-make-emacs-variables-local)
-        (condition-case err
-
-            (while init
-              (setq current-var (caar init))
-              (set (caar init) (eval (cadar init)))
-              (setq init (cdr init)))
-
-          (error
-           (if current-var
-               (message
-                "Eval error in the `c-lang-defvar' or `c-lang-setver' for `%s'
(source eval): %S"
-                current-var err)
-             (signal (car err) (cdr err)))))))
-    ))
+(defun c-make-emacs-variables-local ()
+  (mapcar (lambda (init)
+            (make-local-variable (car init)))
+          (cdr c-emacs-variable-inits)))
+
+(defun c-init-language-vars-for (mode)
+  "Initialize the cc-mode language variables for MODE.
+MODE is a symbol naming the mode to initialize."
+  (let ((c-buffer-is-cc-mode mode)
+        (init (append (cdr c-emacs-variable-inits)
+                      (cdr c-lang-variable-inits)))
+        current-var)
+    (c-make-emacs-variables-local)
+    (condition-case err
+        (while init
+          (setq current-var (caar init))
+          (set (caar init) (eval (cadar init) nil))
+          (setq init (cdr init)))
+      (error
+       (if current-var
+           (message
+            "Eval error in the `c-lang-defvar' or `c-lang-setver' for
`%s' (source eval): %S"
+            current-var err)
+         (signal (car err) (cdr err)))))))

 (defmacro c-init-language-vars (mode)
   "Initialize all the language dependent variables for the given mode.
-This macro is expanded at compile time to a form tailored for the mode
-in question, so MODE must be a constant.  Therefore MODE is not
-evaluated and should not be quoted."
-  `(funcall ,(c-make-init-lang-vars-fun mode)))
+MODE is not evaluated and should not be quoted.  This macro used
+to produce an optimized initialization tailored to MODE, but that
+optimization is no longer worth it.  Use
+`c-init-language-vars-for' instead."
+  `(c-init-language-vars-for ',mode))

 
 (cc-provide 'cc-langs)

=== modified file 'lisp/progmodes/cc-mode.el'
--- lisp/progmodes/cc-mode.el   2014-03-04 04:03:34 +0000
+++ lisp/progmodes/cc-mode.el   2014-05-02 01:20:44 +0000
@@ -149,21 +149,6 @@
 (defun c-leave-cc-mode-mode ()
   (setq c-buffer-is-cc-mode nil))

-(defun c-init-language-vars-for (mode)
-  "Initialize the language variables for one of the language modes
-directly supported by CC Mode.  This can be used instead of the
-`c-init-language-vars' macro if the language you want to use is one of
-those, rather than a derived language defined through the language
-variable system (see \"cc-langs.el\")."
-  (cond ((eq mode 'c-mode)    (c-init-language-vars c-mode))
-       ((eq mode 'c++-mode)  (c-init-language-vars c++-mode))
-       ((eq mode 'objc-mode) (c-init-language-vars objc-mode))
-       ((eq mode 'java-mode) (c-init-language-vars java-mode))
-       ((eq mode 'idl-mode)  (c-init-language-vars idl-mode))
-       ((eq mode 'pike-mode) (c-init-language-vars pike-mode))
-       ((eq mode 'awk-mode)  (c-init-language-vars awk-mode))
-       (t (error "Unsupported mode %s" mode))))
-
 ;;;###autoload
 (defun c-initialize-cc-mode (&optional new-style-init)
   "Initialize CC Mode for use in the current buffer.

=== modified file 'lisp/progmodes/cc-vars.el'
--- lisp/progmodes/cc-vars.el   2014-01-01 07:43:34 +0000
+++ lisp/progmodes/cc-vars.el   2014-05-02 05:24:28 +0000
@@ -1614,6 +1614,75 @@
   :group 'c)

 
+
+(define-widget 'c-extra-keywords-widget 'lazy
+  "Internal CC Mode widget for the `*-extra-keywords' variables."
+  :type '(repeat
+          (cons
+           (string :tag "Keyword")
+           (boolean :tag "Parenthesized expression follows"))))
+
+(defun c-make-extra-keywords-blurb (mode1 mode2)
+  (concat "\
+*List of extra keywords to recognize in "
+          mode1 " mode.
+Each list item should be a cons (KW . PAREN).
+KW should be a string naming a single identifier.
+PAREN should be nil or t.  If t, expect the a parenthesized expression
+after KW and skip over it.
+
+Note that this variable is only consulted when the major mode is
+initialized.  If you change it later you have to reinitialize CC
+Mode by doing \\[" mode2 "].  Additionally, if you change this
+variable outside of customize, you need to call
+`c-clear-value-cache' to make your changes take effect."))
+
+(defun c-extra-keywords-setter (sym val)
+  (set-default sym val)
+  (c-clear-value-cache))
+
+(defcustom c-extra-keywords
+  nil
+  (c-make-extra-keywords-blurb "C" "c-mode")
+  :type 'c-extra-keywords-widget
+  :set 'c-extra-keywords-setter
+  :group 'c)
+
+(defcustom c++-extra-keywords
+  nil
+  (c-make-extra-keywords-blurb "C++" "c++-mode")
+  :type 'c-extra-keywords-widget
+  :set 'c-extra-keywords-setter
+  :group 'c)
+
+(defcustom objc-extra-keywords
+  nil
+  (c-make-extra-keywords-blurb "ObjC" "objc-mode")
+  :type 'c-extra-keywords-widget
+  :set 'c-extra-keywords-setter
+  :group 'c)
+
+(defcustom java-extra-keywords
+  nil
+  (c-make-extra-keywords-blurb "Java" "java-mode")
+  :type 'c-extra-keywords-widget
+  :set 'c-extra-keywords-setter
+  :group 'c)
+
+(defcustom idl-extra-keywords nil
+  nil
+  :type 'c-extra-keywords-widget
+  :set 'c-extra-keywords-setter
+  :group 'c)
+
+(defcustom pike-extra-keywords
+  nil
+  (c-make-extra-keywords-blurb "Pike" "pike-mode")
+  :type 'c-extra-keywords-widget
+  :set 'c-extra-keywords-setter
+  :group 'c)
+
+
 ;; Non-customizable variables, still part of the interface to CC Mode
 (defvar c-macro-with-semi-re nil
   ;; Regular expression which matches a (#define'd) symbol whose expansion


Attachment: signature.asc
Description: OpenPGP digital signature


reply via email to

[Prev in Thread] Current Thread [Next in Thread]